xref: /llvm-project/llvm/unittests/Frontend/OpenMPDecompositionTest.cpp (revision a0c590795eae48903a013699fe84db335854fa80)
1 //===- llvm/unittests/Frontend/OpenMPDecompositionTest.cpp ----------------===//
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 #include "llvm/ADT/ArrayRef.h"
10 #include "llvm/ADT/STLExtras.h"
11 #include "llvm/ADT/SmallVector.h"
12 #include "llvm/Frontend/OpenMP/ClauseT.h"
13 #include "llvm/Frontend/OpenMP/ConstructDecompositionT.h"
14 #include "llvm/Frontend/OpenMP/OMP.h"
15 #include "gtest/gtest.h"
16 
17 #include <iterator>
18 #include <optional>
19 #include <sstream>
20 #include <string>
21 #include <tuple>
22 #include <type_traits>
23 #include <utility>
24 
25 // The actual tests start at comment "--- Test" below.
26 
27 // Create simple instantiations of all clauses to allow manual construction
28 // of clauses, and implement emitting of a directive with clauses to a string.
29 //
30 // The tests then follow the pattern
31 // 1. Create a list of clauses.
32 // 2. Pass them, together with a construct, to the decomposition class.
33 // 3. Extract individual resulting leaf constructs with clauses applied
34 //    to them.
35 // 4. Convert them to strings and compare with expected outputs.
36 
37 namespace omp {
38 struct TypeTy {}; // placeholder
39 struct ExprTy {}; // placeholder
40 using IdTy = std::string;
41 } // namespace omp
42 
43 namespace tomp::type {
44 template <> struct ObjectT<omp::IdTy, omp::ExprTy> {
45   const omp::IdTy &id() const { return name; }
46   const std::optional<omp::ExprTy> ref() const { return omp::ExprTy{}; }
47 
48   omp::IdTy name;
49 };
50 } // namespace tomp::type
51 
52 namespace omp {
53 template <typename ElemTy> using List = tomp::type::ListT<ElemTy>;
54 
55 using Object = tomp::ObjectT<IdTy, ExprTy>;
56 
57 namespace clause {
58 using DefinedOperator = tomp::type::DefinedOperatorT<IdTy, ExprTy>;
59 using ProcedureDesignator = tomp::type::ProcedureDesignatorT<IdTy, ExprTy>;
60 using ReductionOperator = tomp::type::ReductionIdentifierT<IdTy, ExprTy>;
61 
62 using AcqRel = tomp::clause::AcqRelT<TypeTy, IdTy, ExprTy>;
63 using Acquire = tomp::clause::AcquireT<TypeTy, IdTy, ExprTy>;
64 using AdjustArgs = tomp::clause::AdjustArgsT<TypeTy, IdTy, ExprTy>;
65 using Affinity = tomp::clause::AffinityT<TypeTy, IdTy, ExprTy>;
66 using Aligned = tomp::clause::AlignedT<TypeTy, IdTy, ExprTy>;
67 using Align = tomp::clause::AlignT<TypeTy, IdTy, ExprTy>;
68 using Allocate = tomp::clause::AllocateT<TypeTy, IdTy, ExprTy>;
69 using Allocator = tomp::clause::AllocatorT<TypeTy, IdTy, ExprTy>;
70 using AppendArgs = tomp::clause::AppendArgsT<TypeTy, IdTy, ExprTy>;
71 using AtomicDefaultMemOrder =
72     tomp::clause::AtomicDefaultMemOrderT<TypeTy, IdTy, ExprTy>;
73 using At = tomp::clause::AtT<TypeTy, IdTy, ExprTy>;
74 using Bind = tomp::clause::BindT<TypeTy, IdTy, ExprTy>;
75 using Capture = tomp::clause::CaptureT<TypeTy, IdTy, ExprTy>;
76 using Collapse = tomp::clause::CollapseT<TypeTy, IdTy, ExprTy>;
77 using Compare = tomp::clause::CompareT<TypeTy, IdTy, ExprTy>;
78 using Copyin = tomp::clause::CopyinT<TypeTy, IdTy, ExprTy>;
79 using Copyprivate = tomp::clause::CopyprivateT<TypeTy, IdTy, ExprTy>;
80 using Defaultmap = tomp::clause::DefaultmapT<TypeTy, IdTy, ExprTy>;
81 using Default = tomp::clause::DefaultT<TypeTy, IdTy, ExprTy>;
82 using Depend = tomp::clause::DependT<TypeTy, IdTy, ExprTy>;
83 using Destroy = tomp::clause::DestroyT<TypeTy, IdTy, ExprTy>;
84 using Detach = tomp::clause::DetachT<TypeTy, IdTy, ExprTy>;
85 using Device = tomp::clause::DeviceT<TypeTy, IdTy, ExprTy>;
86 using DeviceType = tomp::clause::DeviceTypeT<TypeTy, IdTy, ExprTy>;
87 using DistSchedule = tomp::clause::DistScheduleT<TypeTy, IdTy, ExprTy>;
88 using Doacross = tomp::clause::DoacrossT<TypeTy, IdTy, ExprTy>;
89 using DynamicAllocators =
90     tomp::clause::DynamicAllocatorsT<TypeTy, IdTy, ExprTy>;
91 using Enter = tomp::clause::EnterT<TypeTy, IdTy, ExprTy>;
92 using Exclusive = tomp::clause::ExclusiveT<TypeTy, IdTy, ExprTy>;
93 using Fail = tomp::clause::FailT<TypeTy, IdTy, ExprTy>;
94 using Filter = tomp::clause::FilterT<TypeTy, IdTy, ExprTy>;
95 using Final = tomp::clause::FinalT<TypeTy, IdTy, ExprTy>;
96 using Firstprivate = tomp::clause::FirstprivateT<TypeTy, IdTy, ExprTy>;
97 using From = tomp::clause::FromT<TypeTy, IdTy, ExprTy>;
98 using Full = tomp::clause::FullT<TypeTy, IdTy, ExprTy>;
99 using Grainsize = tomp::clause::GrainsizeT<TypeTy, IdTy, ExprTy>;
100 using HasDeviceAddr = tomp::clause::HasDeviceAddrT<TypeTy, IdTy, ExprTy>;
101 using Hint = tomp::clause::HintT<TypeTy, IdTy, ExprTy>;
102 using If = tomp::clause::IfT<TypeTy, IdTy, ExprTy>;
103 using Inbranch = tomp::clause::InbranchT<TypeTy, IdTy, ExprTy>;
104 using Inclusive = tomp::clause::InclusiveT<TypeTy, IdTy, ExprTy>;
105 using Indirect = tomp::clause::IndirectT<TypeTy, IdTy, ExprTy>;
106 using Init = tomp::clause::InitT<TypeTy, IdTy, ExprTy>;
107 using InReduction = tomp::clause::InReductionT<TypeTy, IdTy, ExprTy>;
108 using IsDevicePtr = tomp::clause::IsDevicePtrT<TypeTy, IdTy, ExprTy>;
109 using Lastprivate = tomp::clause::LastprivateT<TypeTy, IdTy, ExprTy>;
110 using Linear = tomp::clause::LinearT<TypeTy, IdTy, ExprTy>;
111 using Link = tomp::clause::LinkT<TypeTy, IdTy, ExprTy>;
112 using Map = tomp::clause::MapT<TypeTy, IdTy, ExprTy>;
113 using Match = tomp::clause::MatchT<TypeTy, IdTy, ExprTy>;
114 using Mergeable = tomp::clause::MergeableT<TypeTy, IdTy, ExprTy>;
115 using Message = tomp::clause::MessageT<TypeTy, IdTy, ExprTy>;
116 using Nocontext = tomp::clause::NocontextT<TypeTy, IdTy, ExprTy>;
117 using Nogroup = tomp::clause::NogroupT<TypeTy, IdTy, ExprTy>;
118 using Nontemporal = tomp::clause::NontemporalT<TypeTy, IdTy, ExprTy>;
119 using Notinbranch = tomp::clause::NotinbranchT<TypeTy, IdTy, ExprTy>;
120 using Novariants = tomp::clause::NovariantsT<TypeTy, IdTy, ExprTy>;
121 using Nowait = tomp::clause::NowaitT<TypeTy, IdTy, ExprTy>;
122 using NumTasks = tomp::clause::NumTasksT<TypeTy, IdTy, ExprTy>;
123 using NumTeams = tomp::clause::NumTeamsT<TypeTy, IdTy, ExprTy>;
124 using NumThreads = tomp::clause::NumThreadsT<TypeTy, IdTy, ExprTy>;
125 using OmpxAttribute = tomp::clause::OmpxAttributeT<TypeTy, IdTy, ExprTy>;
126 using OmpxBare = tomp::clause::OmpxBareT<TypeTy, IdTy, ExprTy>;
127 using OmpxDynCgroupMem = tomp::clause::OmpxDynCgroupMemT<TypeTy, IdTy, ExprTy>;
128 using Ordered = tomp::clause::OrderedT<TypeTy, IdTy, ExprTy>;
129 using Order = tomp::clause::OrderT<TypeTy, IdTy, ExprTy>;
130 using Partial = tomp::clause::PartialT<TypeTy, IdTy, ExprTy>;
131 using Priority = tomp::clause::PriorityT<TypeTy, IdTy, ExprTy>;
132 using Private = tomp::clause::PrivateT<TypeTy, IdTy, ExprTy>;
133 using ProcBind = tomp::clause::ProcBindT<TypeTy, IdTy, ExprTy>;
134 using Read = tomp::clause::ReadT<TypeTy, IdTy, ExprTy>;
135 using Reduction = tomp::clause::ReductionT<TypeTy, IdTy, ExprTy>;
136 using Relaxed = tomp::clause::RelaxedT<TypeTy, IdTy, ExprTy>;
137 using Release = tomp::clause::ReleaseT<TypeTy, IdTy, ExprTy>;
138 using ReverseOffload = tomp::clause::ReverseOffloadT<TypeTy, IdTy, ExprTy>;
139 using Safelen = tomp::clause::SafelenT<TypeTy, IdTy, ExprTy>;
140 using Schedule = tomp::clause::ScheduleT<TypeTy, IdTy, ExprTy>;
141 using SeqCst = tomp::clause::SeqCstT<TypeTy, IdTy, ExprTy>;
142 using Severity = tomp::clause::SeverityT<TypeTy, IdTy, ExprTy>;
143 using Shared = tomp::clause::SharedT<TypeTy, IdTy, ExprTy>;
144 using Simdlen = tomp::clause::SimdlenT<TypeTy, IdTy, ExprTy>;
145 using Simd = tomp::clause::SimdT<TypeTy, IdTy, ExprTy>;
146 using Sizes = tomp::clause::SizesT<TypeTy, IdTy, ExprTy>;
147 using TaskReduction = tomp::clause::TaskReductionT<TypeTy, IdTy, ExprTy>;
148 using ThreadLimit = tomp::clause::ThreadLimitT<TypeTy, IdTy, ExprTy>;
149 using Threads = tomp::clause::ThreadsT<TypeTy, IdTy, ExprTy>;
150 using To = tomp::clause::ToT<TypeTy, IdTy, ExprTy>;
151 using UnifiedAddress = tomp::clause::UnifiedAddressT<TypeTy, IdTy, ExprTy>;
152 using UnifiedSharedMemory =
153     tomp::clause::UnifiedSharedMemoryT<TypeTy, IdTy, ExprTy>;
154 using Uniform = tomp::clause::UniformT<TypeTy, IdTy, ExprTy>;
155 using Unknown = tomp::clause::UnknownT<TypeTy, IdTy, ExprTy>;
156 using Untied = tomp::clause::UntiedT<TypeTy, IdTy, ExprTy>;
157 using Update = tomp::clause::UpdateT<TypeTy, IdTy, ExprTy>;
158 using UseDeviceAddr = tomp::clause::UseDeviceAddrT<TypeTy, IdTy, ExprTy>;
159 using UseDevicePtr = tomp::clause::UseDevicePtrT<TypeTy, IdTy, ExprTy>;
160 using UsesAllocators = tomp::clause::UsesAllocatorsT<TypeTy, IdTy, ExprTy>;
161 using Use = tomp::clause::UseT<TypeTy, IdTy, ExprTy>;
162 using Weak = tomp::clause::WeakT<TypeTy, IdTy, ExprTy>;
163 using When = tomp::clause::WhenT<TypeTy, IdTy, ExprTy>;
164 using Write = tomp::clause::WriteT<TypeTy, IdTy, ExprTy>;
165 } // namespace clause
166 
167 struct Helper {
168   std::optional<Object> getBaseObject(const Object &object) {
169     return std::nullopt;
170   }
171   std::optional<Object> getLoopIterVar() { return std::nullopt; }
172 };
173 
174 using Clause = tomp::ClauseT<TypeTy, IdTy, ExprTy>;
175 using ConstructDecomposition = tomp::ConstructDecompositionT<Clause, Helper>;
176 using DirectiveWithClauses = tomp::DirectiveWithClauses<Clause>;
177 } // namespace omp
178 
179 struct StringifyClause {
180   static std::string join(const omp::List<std::string> &Strings) {
181     std::stringstream Stream;
182     for (const auto &[Index, String] : llvm::enumerate(Strings)) {
183       if (Index != 0)
184         Stream << ", ";
185       Stream << String;
186     }
187     return Stream.str();
188   }
189 
190   static std::string to_str(llvm::omp::Directive D) {
191     return getOpenMPDirectiveName(D).str();
192   }
193   static std::string to_str(llvm::omp::Clause C) {
194     return getOpenMPClauseName(C).str();
195   }
196   static std::string to_str(const omp::TypeTy &Type) { return "type"; }
197   static std::string to_str(const omp::ExprTy &Expr) { return "expr"; }
198   static std::string to_str(const omp::Object &Obj) { return Obj.id(); }
199 
200   template <typename U>
201   static std::enable_if_t<std::is_enum_v<llvm::remove_cvref_t<U>>, std::string>
202   to_str(U &&Item) {
203     return std::to_string(llvm::to_underlying(Item));
204   }
205 
206   template <typename U> static std::string to_str(const omp::List<U> &Items) {
207     omp::List<std::string> Names;
208     llvm::transform(Items, std::back_inserter(Names),
209                     [](auto &&S) { return to_str(S); });
210     return "(" + join(Names) + ")";
211   }
212 
213   template <typename U>
214   static std::string to_str(const std::optional<U> &Item) {
215     if (Item)
216       return to_str(*Item);
217     return "";
218   }
219 
220   template <typename... Us, size_t... Is>
221   static std::string to_str(const std::tuple<Us...> &Tuple,
222                             std::index_sequence<Is...>) {
223     omp::List<std::string> Strings;
224     (Strings.push_back(to_str(std::get<Is>(Tuple))), ...);
225     return "(" + join(Strings) + ")";
226   }
227 
228   template <typename U>
229   static std::enable_if_t<llvm::remove_cvref_t<U>::EmptyTrait::value,
230                           std::string>
231   to_str(U &&Item) {
232     return "";
233   }
234 
235   template <typename U>
236   static std::enable_if_t<llvm::remove_cvref_t<U>::IncompleteTrait::value,
237                           std::string>
238   to_str(U &&Item) {
239     return "";
240   }
241 
242   template <typename U>
243   static std::enable_if_t<llvm::remove_cvref_t<U>::WrapperTrait::value,
244                           std::string>
245   to_str(U &&Item) {
246     // For a wrapper, stringify the wrappee, and only add parentheses if
247     // there aren't any already.
248     std::string Str = to_str(Item.v);
249     if (!Str.empty()) {
250       if (Str.front() == '(' && Str.back() == ')')
251         return Str;
252     }
253     return "(" + to_str(Item.v) + ")";
254   }
255 
256   template <typename U>
257   static std::enable_if_t<llvm::remove_cvref_t<U>::TupleTrait::value,
258                           std::string>
259   to_str(U &&Item) {
260     constexpr size_t TupleSize =
261         std::tuple_size_v<llvm::remove_cvref_t<decltype(Item.t)>>;
262     return to_str(Item.t, std::make_index_sequence<TupleSize>{});
263   }
264 
265   template <typename U>
266   static std::enable_if_t<llvm::remove_cvref_t<U>::UnionTrait::value,
267                           std::string>
268   to_str(U &&Item) {
269     return std::visit([](auto &&S) { return to_str(S); }, Item.u);
270   }
271 
272   StringifyClause(const omp::Clause &C)
273       // Rely on content stringification to emit enclosing parentheses.
274       : Str(to_str(C.id) + to_str(C)) {}
275 
276   std::string Str;
277 };
278 
279 std::string stringify(const omp::DirectiveWithClauses &DWC) {
280   std::stringstream Stream;
281 
282   Stream << getOpenMPDirectiveName(DWC.id).str();
283   for (const omp::Clause &C : DWC.clauses)
284     Stream << ' ' << StringifyClause(C).Str;
285 
286   return Stream.str();
287 }
288 
289 // --- Tests ----------------------------------------------------------
290 
291 namespace red {
292 // Make it easier to construct reduction operators from built-in intrinsics.
293 omp::clause::ReductionOperator
294 makeOp(omp::clause::DefinedOperator::IntrinsicOperator Op) {
295   return omp::clause::ReductionOperator{omp::clause::DefinedOperator{Op}};
296 }
297 } // namespace red
298 
299 namespace {
300 using namespace llvm::omp;
301 
302 class OpenMPDecompositionTest : public testing::Test {
303 protected:
304   void SetUp() override {}
305   void TearDown() override {}
306 
307   omp::Helper Helper;
308   uint32_t AnyVersion = 999;
309 };
310 
311 // PRIVATE
312 // [5.2:111:5-7]
313 // Directives: distribute, do, for, loop, parallel, scope, sections, simd,
314 // single, target, task, taskloop, teams
315 //
316 // [5.2:340:1-2]
317 // (1) The effect of the 1 private clause is as if it is applied only to the
318 // innermost leaf construct that permits it.
319 TEST_F(OpenMPDecompositionTest, Private1) {
320   omp::Object x{"x"};
321 
322   omp::List<omp::Clause> Clauses{
323       {OMPC_private, omp::clause::Private{{x}}},
324   };
325 
326   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
327                                   Clauses);
328   ASSERT_EQ(Dec.output.size(), 2u);
329 
330   std::string Dir0 = stringify(Dec.output[0]);
331   std::string Dir1 = stringify(Dec.output[1]);
332   ASSERT_EQ(Dir0, "parallel");            // (1)
333   ASSERT_EQ(Dir1, "sections private(x)"); // (1)
334 }
335 
336 TEST_F(OpenMPDecompositionTest, Private2) {
337   omp::Object x{"x"};
338 
339   omp::List<omp::Clause> Clauses{
340       {OMPC_private, omp::clause::Private{{x}}},
341   };
342 
343   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_masked,
344                                   Clauses);
345   ASSERT_EQ(Dec.output.size(), 2u);
346 
347   std::string Dir0 = stringify(Dec.output[0]);
348   std::string Dir1 = stringify(Dec.output[1]);
349   ASSERT_EQ(Dir0, "parallel private(x)"); // (1)
350   ASSERT_EQ(Dir1, "masked");              // (1)
351 }
352 
353 // FIRSTPRIVATE
354 // [5.2:112:5-7]
355 // Directives: distribute, do, for, parallel, scope, sections, single, target,
356 // task, taskloop, teams
357 //
358 // [5.2:340:3-20]
359 // (3) The effect of the firstprivate clause is as if it is applied to one or
360 // more leaf constructs as follows:
361 //  (5) To the distribute construct if it is among the constituent constructs;
362 //  (6) To the teams construct if it is among the constituent constructs and the
363 //      distribute construct is not;
364 //  (8) To a worksharing construct that accepts the clause if one is among the
365 //      constituent constructs;
366 //  (9) To the taskloop construct if it is among the constituent constructs;
367 // (10) To the parallel construct if it is among the constituent constructs and
368 //      neither a taskloop construct nor a worksharing construct that accepts
369 //      the clause is among them;
370 // (12) To the target construct if it is among the constituent constructs and
371 //      the same list item neither appears in a lastprivate clause nor is the
372 //      base variable or base pointer of a list item that appears in a map
373 //      clause.
374 //
375 // (15) If the parallel construct is among the constituent constructs and the
376 // effect is not as if the firstprivate clause is applied to it by the above
377 // rules, then the effect is as if the shared clause with the same list item is
378 // applied to the parallel construct.
379 // (17) If the teams construct is among the constituent constructs and the
380 // effect is not as if the firstprivate clause is applied to it by the above
381 // rules, then the effect is as if the shared clause with the same list item is
382 // applied to the teams construct.
383 TEST_F(OpenMPDecompositionTest, Firstprivate1) {
384   omp::Object x{"x"};
385 
386   omp::List<omp::Clause> Clauses{
387       {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
388   };
389 
390   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
391                                   Clauses);
392   ASSERT_EQ(Dec.output.size(), 2u);
393 
394   std::string Dir0 = stringify(Dec.output[0]);
395   std::string Dir1 = stringify(Dec.output[1]);
396   ASSERT_EQ(Dir0, "parallel shared(x)");       // (10), (15)
397   ASSERT_EQ(Dir1, "sections firstprivate(x)"); // (8)
398 }
399 
400 TEST_F(OpenMPDecompositionTest, Firstprivate2) {
401   omp::Object x{"x"};
402 
403   omp::List<omp::Clause> Clauses{
404       {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
405   };
406 
407   omp::ConstructDecomposition Dec(AnyVersion, Helper,
408                                   OMPD_target_teams_distribute, Clauses);
409   ASSERT_EQ(Dec.output.size(), 3u);
410 
411   std::string Dir0 = stringify(Dec.output[0]);
412   std::string Dir1 = stringify(Dec.output[1]);
413   std::string Dir2 = stringify(Dec.output[2]);
414   ASSERT_EQ(Dir0, "target firstprivate(x)");     // (12)
415   ASSERT_EQ(Dir1, "teams shared(x)");            // (6), (17)
416   ASSERT_EQ(Dir2, "distribute firstprivate(x)"); // (5)
417 }
418 
419 TEST_F(OpenMPDecompositionTest, Firstprivate3) {
420   omp::Object x{"x"};
421 
422   omp::List<omp::Clause> Clauses{
423       {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
424       {OMPC_lastprivate, omp::clause::Lastprivate{{std::nullopt, {x}}}},
425   };
426 
427   omp::ConstructDecomposition Dec(AnyVersion, Helper,
428                                   OMPD_target_teams_distribute, Clauses);
429   ASSERT_EQ(Dec.output.size(), 3u);
430 
431   std::string Dir0 = stringify(Dec.output[0]);
432   std::string Dir1 = stringify(Dec.output[1]);
433   std::string Dir2 = stringify(Dec.output[2]);
434   ASSERT_EQ(Dir0, "target map(2, , , , (x))"); // (12), (27)
435   ASSERT_EQ(Dir1, "teams shared(x)");          // (6), (17)
436   ASSERT_EQ(Dir2, "distribute firstprivate(x) lastprivate(, (x))"); // (5), (21)
437 }
438 
439 TEST_F(OpenMPDecompositionTest, Firstprivate4) {
440   omp::Object x{"x"};
441 
442   omp::List<omp::Clause> Clauses{
443       {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
444   };
445 
446   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_teams,
447                                   Clauses);
448   ASSERT_EQ(Dec.output.size(), 2u);
449 
450   std::string Dir0 = stringify(Dec.output[0]);
451   std::string Dir1 = stringify(Dec.output[1]);
452   ASSERT_EQ(Dir0, "target firstprivate(x)"); // (12)
453   ASSERT_EQ(Dir1, "teams firstprivate(x)");  // (6)
454 }
455 
456 TEST_F(OpenMPDecompositionTest, Firstprivate5) {
457   omp::Object x{"x"};
458 
459   omp::List<omp::Clause> Clauses{
460       {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
461   };
462 
463   omp::ConstructDecomposition Dec(AnyVersion, Helper,
464                                   OMPD_parallel_masked_taskloop, Clauses);
465   ASSERT_EQ(Dec.output.size(), 3u);
466 
467   std::string Dir0 = stringify(Dec.output[0]);
468   std::string Dir1 = stringify(Dec.output[1]);
469   std::string Dir2 = stringify(Dec.output[2]);
470   ASSERT_EQ(Dir0, "parallel shared(x)"); // (10)
471   ASSERT_EQ(Dir1, "masked");
472   ASSERT_EQ(Dir2, "taskloop firstprivate(x)"); // (9)
473 }
474 
475 TEST_F(OpenMPDecompositionTest, Firstprivate6) {
476   omp::Object x{"x"};
477 
478   omp::List<omp::Clause> Clauses{
479       {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
480   };
481 
482   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_masked,
483                                   Clauses);
484   ASSERT_EQ(Dec.output.size(), 2u);
485 
486   std::string Dir0 = stringify(Dec.output[0]);
487   std::string Dir1 = stringify(Dec.output[1]);
488   ASSERT_EQ(Dir0, "parallel firstprivate(x)"); // (10)
489   ASSERT_EQ(Dir1, "masked");
490 }
491 
492 TEST_F(OpenMPDecompositionTest, Firstprivate7) {
493   omp::Object x{"x"};
494 
495   omp::List<omp::Clause> Clauses{
496       {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
497   };
498 
499   // Composite constructs are still decomposed.
500   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_teams_distribute,
501                                   Clauses);
502   ASSERT_EQ(Dec.output.size(), 2u);
503 
504   std::string Dir0 = stringify(Dec.output[0]);
505   std::string Dir1 = stringify(Dec.output[1]);
506   ASSERT_EQ(Dir0, "teams shared(x)");            // (17)
507   ASSERT_EQ(Dir1, "distribute firstprivate(x)"); // (5)
508 }
509 
510 // LASTPRIVATE
511 // [5.2:115:7-8]
512 // Directives: distribute, do, for, loop, sections, simd, taskloop
513 //
514 // [5.2:340:21-30]
515 // (21) The effect of the lastprivate clause is as if it is applied to all leaf
516 // constructs that permit the clause.
517 // (22) If the parallel construct is among the constituent constructs and the
518 // list item is not also specified in the firstprivate clause, then the effect
519 // of the lastprivate clause is as if the shared clause with the same list item
520 // is applied to the parallel construct.
521 // (24) If the teams construct is among the constituent constructs and the list
522 // item is not also specified in the firstprivate clause, then the effect of the
523 // lastprivate clause is as if the shared clause with the same list item is
524 // applied to the teams construct.
525 // (27) If the target construct is among the constituent constructs and the list
526 // item is not the base variable or base pointer of a list item that appears in
527 // a map clause, the effect of the lastprivate clause is as if the same list
528 // item appears in a map clause with a map-type of tofrom.
529 TEST_F(OpenMPDecompositionTest, Lastprivate1) {
530   omp::Object x{"x"};
531 
532   omp::List<omp::Clause> Clauses{
533       {OMPC_lastprivate, omp::clause::Lastprivate{{std::nullopt, {x}}}},
534   };
535 
536   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
537                                   Clauses);
538   ASSERT_EQ(Dec.output.size(), 2u);
539 
540   std::string Dir0 = stringify(Dec.output[0]);
541   std::string Dir1 = stringify(Dec.output[1]);
542   ASSERT_EQ(Dir0, "parallel shared(x)");          // (21), (22)
543   ASSERT_EQ(Dir1, "sections lastprivate(, (x))"); // (21)
544 }
545 
546 TEST_F(OpenMPDecompositionTest, Lastprivate2) {
547   omp::Object x{"x"};
548 
549   omp::List<omp::Clause> Clauses{
550       {OMPC_lastprivate, omp::clause::Lastprivate{{std::nullopt, {x}}}},
551   };
552 
553   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_teams_distribute,
554                                   Clauses);
555   ASSERT_EQ(Dec.output.size(), 2u);
556 
557   std::string Dir0 = stringify(Dec.output[0]);
558   std::string Dir1 = stringify(Dec.output[1]);
559   ASSERT_EQ(Dir0, "teams shared(x)");               // (21), (25)
560   ASSERT_EQ(Dir1, "distribute lastprivate(, (x))"); // (21)
561 }
562 
563 TEST_F(OpenMPDecompositionTest, Lastprivate3) {
564   omp::Object x{"x"};
565 
566   omp::List<omp::Clause> Clauses{
567       {OMPC_lastprivate, omp::clause::Lastprivate{{std::nullopt, {x}}}},
568   };
569 
570   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel_do,
571                                   Clauses);
572   ASSERT_EQ(Dec.output.size(), 3u);
573 
574   std::string Dir0 = stringify(Dec.output[0]);
575   std::string Dir1 = stringify(Dec.output[1]);
576   std::string Dir2 = stringify(Dec.output[2]);
577   ASSERT_EQ(Dir0, "target map(2, , , , (x))"); // (21), (27)
578   ASSERT_EQ(Dir1, "parallel shared(x)");       // (22)
579   ASSERT_EQ(Dir2, "do lastprivate(, (x))");    // (21)
580 }
581 
582 // SHARED
583 // [5.2:110:5-6]
584 // Directives: parallel, task, taskloop, teams
585 //
586 // [5.2:340:31-32]
587 // (31) The effect of the shared, default, thread_limit, or order clause is as
588 // if it is applied to all leaf constructs that permit the clause.
589 TEST_F(OpenMPDecompositionTest, Shared1) {
590   omp::Object x{"x"};
591 
592   omp::List<omp::Clause> Clauses{
593       {OMPC_shared, omp::clause::Shared{{x}}},
594   };
595 
596   omp::ConstructDecomposition Dec(AnyVersion, Helper,
597                                   OMPD_parallel_masked_taskloop, Clauses);
598   ASSERT_EQ(Dec.output.size(), 3u);
599 
600   std::string Dir0 = stringify(Dec.output[0]);
601   std::string Dir1 = stringify(Dec.output[1]);
602   std::string Dir2 = stringify(Dec.output[2]);
603   ASSERT_EQ(Dir0, "parallel shared(x)"); // (31)
604   ASSERT_EQ(Dir1, "masked");             // (31)
605   ASSERT_EQ(Dir2, "taskloop shared(x)"); // (31)
606 }
607 
608 // DEFAULT
609 // [5.2:109:5-6]
610 // Directives: parallel, task, taskloop, teams
611 //
612 // [5.2:340:31-32]
613 // (31) The effect of the shared, default, thread_limit, or order clause is as
614 // if it is applied to all leaf constructs that permit the clause.
615 TEST_F(OpenMPDecompositionTest, Default1) {
616   omp::Object x{"x"};
617 
618   omp::List<omp::Clause> Clauses{
619       {OMPC_default,
620        omp::clause::Default{
621            omp::clause::Default::DataSharingAttribute::Firstprivate}},
622   };
623 
624   omp::ConstructDecomposition Dec(AnyVersion, Helper,
625                                   OMPD_parallel_masked_taskloop, Clauses);
626   ASSERT_EQ(Dec.output.size(), 3u);
627 
628   std::string Dir0 = stringify(Dec.output[0]);
629   std::string Dir1 = stringify(Dec.output[1]);
630   std::string Dir2 = stringify(Dec.output[2]);
631   ASSERT_EQ(Dir0, "parallel default(0)"); // (31)
632   ASSERT_EQ(Dir1, "masked");              // (31)
633   ASSERT_EQ(Dir2, "taskloop default(0)"); // (31)
634 }
635 
636 // THREAD_LIMIT
637 // [5.2:277:14-15]
638 // Directives: target, teams
639 //
640 // [5.2:340:31-32]
641 // (31) The effect of the shared, default, thread_limit, or order clause is as
642 // if it is applied to all leaf constructs that permit the clause.
643 TEST_F(OpenMPDecompositionTest, ThreadLimit1) {
644   omp::Object x{"x"};
645 
646   omp::List<omp::Clause> Clauses{
647       {OMPC_thread_limit, omp::clause::ThreadLimit{omp::ExprTy{}}},
648   };
649 
650   omp::ConstructDecomposition Dec(AnyVersion, Helper,
651                                   OMPD_target_teams_distribute, Clauses);
652   ASSERT_EQ(Dec.output.size(), 3u);
653 
654   std::string Dir0 = stringify(Dec.output[0]);
655   std::string Dir1 = stringify(Dec.output[1]);
656   std::string Dir2 = stringify(Dec.output[2]);
657   ASSERT_EQ(Dir0, "target thread_limit(expr)"); // (31)
658   ASSERT_EQ(Dir1, "teams thread_limit(expr)");  // (31)
659   ASSERT_EQ(Dir2, "distribute");                // (31)
660 }
661 
662 // ORDER
663 // [5.2:234:3-4]
664 // Directives: distribute, do, for, loop, simd
665 //
666 // [5.2:340:31-32]
667 // (31) The effect of the shared, default, thread_limit, or order clause is as
668 // if it is applied to all leaf constructs that permit the clause.
669 TEST_F(OpenMPDecompositionTest, Order1) {
670   omp::Object x{"x"};
671 
672   omp::List<omp::Clause> Clauses{
673       {OMPC_order,
674        omp::clause::Order{{omp::clause::Order::OrderModifier::Unconstrained,
675                            omp::clause::Order::Ordering::Concurrent}}},
676   };
677 
678   omp::ConstructDecomposition Dec(
679       AnyVersion, Helper, OMPD_target_teams_distribute_parallel_for_simd,
680       Clauses);
681   ASSERT_EQ(Dec.output.size(), 6u);
682 
683   std::string Dir0 = stringify(Dec.output[0]);
684   std::string Dir1 = stringify(Dec.output[1]);
685   std::string Dir2 = stringify(Dec.output[2]);
686   std::string Dir3 = stringify(Dec.output[3]);
687   std::string Dir4 = stringify(Dec.output[4]);
688   std::string Dir5 = stringify(Dec.output[5]);
689   ASSERT_EQ(Dir0, "target"); // (31)
690   ASSERT_EQ(Dir1, "teams");  // (31)
691   ASSERT_EQ(Dir2, "distribute order(1, 0)"); // (31)
692   ASSERT_EQ(Dir3, "parallel");         // (31)
693   ASSERT_EQ(Dir4, "for order(1, 0)");  // (31)
694   ASSERT_EQ(Dir5, "simd order(1, 0)"); // (31)
695 }
696 
697 // ALLOCATE
698 // [5.2:178:7-9]
699 // Directives: allocators, distribute, do, for, parallel, scope, sections,
700 // single, target, task, taskgroup, taskloop, teams
701 //
702 // [5.2:340:33-35]
703 // (33) The effect of the allocate clause is as if it is applied to all leaf
704 // constructs that permit the clause and to which a data-sharing attribute
705 // clause that may create a private copy of the same list item is applied.
706 TEST_F(OpenMPDecompositionTest, Allocate1) {
707   omp::Object x{"x"};
708 
709   // Allocate + firstprivate
710   omp::List<omp::Clause> Clauses{
711       {OMPC_allocate,
712        omp::clause::Allocate{{std::nullopt, std::nullopt, std::nullopt, {x}}}},
713       {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
714   };
715 
716   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
717                                   Clauses);
718   ASSERT_EQ(Dec.output.size(), 2u);
719 
720   std::string Dir0 = stringify(Dec.output[0]);
721   std::string Dir1 = stringify(Dec.output[1]);
722   ASSERT_EQ(Dir0, "parallel shared(x)");                           // (33)
723   ASSERT_EQ(Dir1, "sections firstprivate(x) allocate(, , , (x))"); // (33)
724 }
725 
726 TEST_F(OpenMPDecompositionTest, Allocate2) {
727   omp::Object x{"x"};
728   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
729 
730   // Allocate + in_reduction
731   omp::List<omp::Clause> Clauses{
732       {OMPC_allocate,
733        omp::clause::Allocate{{std::nullopt, std::nullopt, std::nullopt, {x}}}},
734       {OMPC_in_reduction, omp::clause::InReduction{{{Add}, {x}}}},
735   };
736 
737   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel,
738                                   Clauses);
739   ASSERT_EQ(Dec.output.size(), 2u);
740 
741   std::string Dir0 = stringify(Dec.output[0]);
742   std::string Dir1 = stringify(Dec.output[1]);
743   ASSERT_EQ(Dir0, "target in_reduction((3), (x)) allocate(, , , (x))"); // (33)
744   ASSERT_EQ(Dir1, "parallel");                                          // (33)
745 }
746 
747 TEST_F(OpenMPDecompositionTest, Allocate3) {
748   omp::Object x{"x"};
749 
750   // Allocate + linear
751   omp::List<omp::Clause> Clauses{
752       {OMPC_allocate,
753        omp::clause::Allocate{{std::nullopt, std::nullopt, std::nullopt, {x}}}},
754       {OMPC_linear,
755        omp::clause::Linear{{std::nullopt, std::nullopt, std::nullopt, {x}}}},
756   };
757 
758   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_for,
759                                   Clauses);
760   ASSERT_EQ(Dec.output.size(), 2u);
761 
762   std::string Dir0 = stringify(Dec.output[0]);
763   std::string Dir1 = stringify(Dec.output[1]);
764   // The "shared" clause is duplicated---this isn't harmful, but it
765   // should be fixed eventually.
766   ASSERT_EQ(Dir0, "parallel shared(x) shared(x)"); // (33)
767   ASSERT_EQ(Dir1, "for linear(, , , (x)) firstprivate(x) lastprivate(, (x)) "
768                   "allocate(, , , (x))"); // (33)
769 }
770 
771 TEST_F(OpenMPDecompositionTest, Allocate4) {
772   omp::Object x{"x"};
773 
774   // Allocate + lastprivate
775   omp::List<omp::Clause> Clauses{
776       {OMPC_allocate,
777        omp::clause::Allocate{{std::nullopt, std::nullopt, std::nullopt, {x}}}},
778       {OMPC_lastprivate, omp::clause::Lastprivate{{std::nullopt, {x}}}},
779   };
780 
781   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
782                                   Clauses);
783   ASSERT_EQ(Dec.output.size(), 2u);
784 
785   std::string Dir0 = stringify(Dec.output[0]);
786   std::string Dir1 = stringify(Dec.output[1]);
787   ASSERT_EQ(Dir0, "parallel shared(x)");                              // (33)
788   ASSERT_EQ(Dir1, "sections lastprivate(, (x)) allocate(, , , (x))"); // (33)
789 }
790 
791 TEST_F(OpenMPDecompositionTest, Allocate5) {
792   omp::Object x{"x"};
793 
794   // Allocate + private
795   omp::List<omp::Clause> Clauses{
796       {OMPC_allocate,
797        omp::clause::Allocate{{std::nullopt, std::nullopt, std::nullopt, {x}}}},
798       {OMPC_private, omp::clause::Private{{x}}},
799   };
800 
801   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
802                                   Clauses);
803   ASSERT_EQ(Dec.output.size(), 2u);
804 
805   std::string Dir0 = stringify(Dec.output[0]);
806   std::string Dir1 = stringify(Dec.output[1]);
807   ASSERT_EQ(Dir0, "parallel");                                // (33)
808   ASSERT_EQ(Dir1, "sections private(x) allocate(, , , (x))"); // (33)
809 }
810 
811 TEST_F(OpenMPDecompositionTest, Allocate6) {
812   omp::Object x{"x"};
813   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
814 
815   // Allocate + reduction
816   omp::List<omp::Clause> Clauses{
817       {OMPC_allocate,
818        omp::clause::Allocate{{std::nullopt, std::nullopt, std::nullopt, {x}}}},
819       {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
820   };
821 
822   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
823                                   Clauses);
824   ASSERT_EQ(Dec.output.size(), 2u);
825 
826   std::string Dir0 = stringify(Dec.output[0]);
827   std::string Dir1 = stringify(Dec.output[1]);
828   ASSERT_EQ(Dir0, "parallel shared(x)");                                 // (33)
829   ASSERT_EQ(Dir1, "sections reduction(, (3), (x)) allocate(, , , (x))"); // (33)
830 }
831 
832 // REDUCTION
833 // [5.2:134:17-18]
834 // Directives: do, for, loop, parallel, scope, sections, simd, taskloop, teams
835 //
836 // [5.2:340-341:36-13]
837 // (36) The effect of the reduction clause is as if it is applied to all leaf
838 // constructs that permit the clause, except for the following constructs:
839 //  (1) The parallel construct, when combined with the sections,
840 //      worksharing-loop, loop, or taskloop construct; and
841 //  (3) The teams construct, when combined with the loop construct.
842 // (4) For the parallel and teams constructs above, the effect of the reduction
843 // clause instead is as if each list item or, for any list item that is an array
844 // item, its corresponding base array or base pointer appears in a shared clause
845 // for the construct.
846 // (6) If the task reduction-modifier is specified, the effect is as if it only
847 // modifies the behavior of the reduction clause on the innermost leaf construct
848 // that accepts the modifier (see Section 5.5.8).
849 // (8) If the inscan reduction-modifier is specified, the effect is as if it
850 // modifies the behavior of the reduction clause on all constructs of the
851 // combined construct to which the clause is applied and that accept the
852 // modifier.
853 // (10) If a list item in a reduction clause on a combined target construct does
854 // not have the same base variable or base pointer as a list item in a map
855 // clause on the construct, then the effect is as if the list item in the
856 // reduction clause appears as a list item in a map clause with a map-type of
857 // tofrom.
858 TEST_F(OpenMPDecompositionTest, Reduction1) {
859   omp::Object x{"x"};
860   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
861 
862   omp::List<omp::Clause> Clauses{
863       {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
864   };
865 
866   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
867                                   Clauses);
868   ASSERT_EQ(Dec.output.size(), 2u);
869 
870   std::string Dir0 = stringify(Dec.output[0]);
871   std::string Dir1 = stringify(Dec.output[1]);
872   ASSERT_EQ(Dir0, "parallel shared(x)");             // (36), (1), (4)
873   ASSERT_EQ(Dir1, "sections reduction(, (3), (x))"); // (36)
874 }
875 
876 TEST_F(OpenMPDecompositionTest, Reduction2) {
877   omp::Object x{"x"};
878   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
879 
880   omp::List<omp::Clause> Clauses{
881       {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
882   };
883 
884   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_masked,
885                                   Clauses);
886   ASSERT_EQ(Dec.output.size(), 2u);
887 
888   std::string Dir0 = stringify(Dec.output[0]);
889   std::string Dir1 = stringify(Dec.output[1]);
890   ASSERT_EQ(Dir0, "parallel reduction(, (3), (x))"); // (36), (1), (4)
891   ASSERT_EQ(Dir1, "masked");                         // (36)
892 }
893 
894 TEST_F(OpenMPDecompositionTest, Reduction3) {
895   omp::Object x{"x"};
896   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
897 
898   omp::List<omp::Clause> Clauses{
899       {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
900   };
901 
902   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_teams_loop, Clauses);
903   ASSERT_EQ(Dec.output.size(), 2u);
904 
905   std::string Dir0 = stringify(Dec.output[0]);
906   std::string Dir1 = stringify(Dec.output[1]);
907   ASSERT_EQ(Dir0, "teams shared(x)");            // (36), (3), (4)
908   ASSERT_EQ(Dir1, "loop reduction(, (3), (x))"); // (36)
909 }
910 
911 TEST_F(OpenMPDecompositionTest, Reduction4) {
912   omp::Object x{"x"};
913   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
914 
915   omp::List<omp::Clause> Clauses{
916       {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
917   };
918 
919   omp::ConstructDecomposition Dec(AnyVersion, Helper,
920                                   OMPD_teams_distribute_parallel_for, Clauses);
921   ASSERT_EQ(Dec.output.size(), 4u);
922 
923   std::string Dir0 = stringify(Dec.output[0]);
924   std::string Dir1 = stringify(Dec.output[1]);
925   std::string Dir2 = stringify(Dec.output[2]);
926   std::string Dir3 = stringify(Dec.output[3]);
927   ASSERT_EQ(Dir0, "teams reduction(, (3), (x))"); // (36), (3)
928   ASSERT_EQ(Dir1, "distribute");                  // (36)
929   ASSERT_EQ(Dir2, "parallel shared(x)");          // (36), (1), (4)
930   ASSERT_EQ(Dir3, "for reduction(, (3), (x))");   // (36)
931 }
932 
933 TEST_F(OpenMPDecompositionTest, Reduction5) {
934   omp::Object x{"x"};
935   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
936   auto TaskMod = omp::clause::Reduction::ReductionModifier::Task;
937 
938   omp::List<omp::Clause> Clauses{
939       {OMPC_reduction, omp::clause::Reduction{{TaskMod, {Add}, {x}}}},
940   };
941 
942   omp::ConstructDecomposition Dec(AnyVersion, Helper,
943                                   OMPD_teams_distribute_parallel_for, Clauses);
944   ASSERT_EQ(Dec.output.size(), 4u);
945 
946   std::string Dir0 = stringify(Dec.output[0]);
947   std::string Dir1 = stringify(Dec.output[1]);
948   std::string Dir2 = stringify(Dec.output[2]);
949   std::string Dir3 = stringify(Dec.output[3]);
950   ASSERT_EQ(Dir0, "teams reduction(, (3), (x))"); // (36), (3), (6)
951   ASSERT_EQ(Dir1, "distribute");                  // (36)
952   ASSERT_EQ(Dir2, "parallel shared(x)");          // (36), (1), (4)
953   ASSERT_EQ(Dir3, "for reduction(2, (3), (x))");  // (36), (6)
954 }
955 
956 TEST_F(OpenMPDecompositionTest, Reduction6) {
957   omp::Object x{"x"};
958   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
959   auto InscanMod = omp::clause::Reduction::ReductionModifier::Inscan;
960 
961   omp::List<omp::Clause> Clauses{
962       {OMPC_reduction, omp::clause::Reduction{{InscanMod, {Add}, {x}}}},
963   };
964 
965   omp::ConstructDecomposition Dec(AnyVersion, Helper,
966                                   OMPD_teams_distribute_parallel_for, Clauses);
967   ASSERT_EQ(Dec.output.size(), 4u);
968 
969   std::string Dir0 = stringify(Dec.output[0]);
970   std::string Dir1 = stringify(Dec.output[1]);
971   std::string Dir2 = stringify(Dec.output[2]);
972   std::string Dir3 = stringify(Dec.output[3]);
973   ASSERT_EQ(Dir0, "teams reduction(, (3), (x))"); // (36), (3), (8)
974   ASSERT_EQ(Dir1, "distribute");                  // (36)
975   ASSERT_EQ(Dir2, "parallel shared(x)");          // (36), (1), (4)
976   ASSERT_EQ(Dir3, "for reduction(1, (3), (x))");  // (36), (8)
977 }
978 
979 TEST_F(OpenMPDecompositionTest, Reduction7) {
980   omp::Object x{"x"};
981   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
982 
983   omp::List<omp::Clause> Clauses{
984       {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
985   };
986 
987   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel_do,
988                                   Clauses);
989   ASSERT_EQ(Dec.output.size(), 3u);
990 
991   std::string Dir0 = stringify(Dec.output[0]);
992   std::string Dir1 = stringify(Dec.output[1]);
993   std::string Dir2 = stringify(Dec.output[2]);
994   ASSERT_EQ(Dir0, "target map(2, , , , (x))"); // (36), (10)
995   ASSERT_EQ(Dir1, "parallel shared(x)");       // (36), (1), (4)
996   ASSERT_EQ(Dir2, "do reduction(, (3), (x))"); // (36)
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 TEST_F(OpenMPDecompositionTest, If1) {
1011   omp::List<omp::Clause> Clauses{
1012       {OMPC_if,
1013        omp::clause::If{{llvm::omp::Directive::OMPD_parallel, omp::ExprTy{}}}},
1014   };
1015 
1016   omp::ConstructDecomposition Dec(AnyVersion, Helper,
1017                                   OMPD_target_parallel_for_simd, Clauses);
1018   ASSERT_EQ(Dec.output.size(), 4u);
1019   std::string Dir0 = stringify(Dec.output[0]);
1020   std::string Dir1 = stringify(Dec.output[1]);
1021   std::string Dir2 = stringify(Dec.output[2]);
1022   std::string Dir3 = stringify(Dec.output[3]);
1023   ASSERT_EQ(Dir0, "target");              // (15)
1024   ASSERT_EQ(Dir1, "parallel if(, expr)"); // (15)
1025   ASSERT_EQ(Dir2, "for");                 // (15)
1026   ASSERT_EQ(Dir3, "simd");                // (15)
1027 }
1028 
1029 TEST_F(OpenMPDecompositionTest, If2) {
1030   omp::List<omp::Clause> Clauses{
1031       {OMPC_if, omp::clause::If{{std::nullopt, omp::ExprTy{}}}},
1032   };
1033 
1034   omp::ConstructDecomposition Dec(AnyVersion, Helper,
1035                                   OMPD_target_parallel_for_simd, Clauses);
1036   ASSERT_EQ(Dec.output.size(), 4u);
1037   std::string Dir0 = stringify(Dec.output[0]);
1038   std::string Dir1 = stringify(Dec.output[1]);
1039   std::string Dir2 = stringify(Dec.output[2]);
1040   std::string Dir3 = stringify(Dec.output[3]);
1041   ASSERT_EQ(Dir0, "target if(, expr)");   // (16)
1042   ASSERT_EQ(Dir1, "parallel if(, expr)"); // (16)
1043   ASSERT_EQ(Dir2, "for");                 // (16)
1044   ASSERT_EQ(Dir3, "simd if(, expr)");     // (16)
1045 }
1046 
1047 // LINEAR
1048 // [5.2:118:1-2]
1049 // Directives: declare simd, do, for, simd
1050 //
1051 // [5.2:341:15-22]
1052 // (15.1) The effect of the linear clause is as if it is applied to the
1053 // innermost leaf construct.
1054 // (15.2) Additionally, if the list item is not the iteration variable of a simd
1055 // or worksharing-loop SIMD construct, the effect on the outer leaf constructs
1056 // is as if the list item was specified in firstprivate and lastprivate clauses
1057 // on the combined or composite construct, with the rules specified above
1058 // applied.
1059 // (19) If a list item of the linear clause is the iteration variable of a simd
1060 // or worksharing-loop SIMD construct and it is not declared in the construct,
1061 // the effect on the outer leaf constructs is as if the list item was specified
1062 // in a lastprivate clause on the combined or composite construct with the rules
1063 // specified above applied.
1064 TEST_F(OpenMPDecompositionTest, Linear1) {
1065   omp::Object x{"x"};
1066 
1067   omp::List<omp::Clause> Clauses{
1068       {OMPC_linear,
1069        omp::clause::Linear{{std::nullopt, std::nullopt, std::nullopt, {x}}}},
1070   };
1071 
1072   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_for_simd, Clauses);
1073   ASSERT_EQ(Dec.output.size(), 2u);
1074   std::string Dir0 = stringify(Dec.output[0]);
1075   std::string Dir1 = stringify(Dec.output[1]);
1076   ASSERT_EQ(Dir0, "for firstprivate(x) lastprivate(, (x))"); // (15.1), (15.2)
1077   ASSERT_EQ(Dir1, "simd linear(, , , (x)) lastprivate(, (x))"); // (15.1)
1078 }
1079 
1080 // NOWAIT
1081 // [5.2:308:11-13]
1082 // Directives: dispatch, do, for, interop, scope, sections, single, target,
1083 // target enter data, target exit data, target update, taskwait, workshare
1084 //
1085 // [5.2:341:23]
1086 // (23) The effect of the nowait clause is as if it is applied to the outermost
1087 // leaf construct that permits it.
1088 TEST_F(OpenMPDecompositionTest, Nowait1) {
1089   omp::List<omp::Clause> Clauses{
1090       {OMPC_nowait, omp::clause::Nowait{}},
1091   };
1092 
1093   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel_for,
1094                                   Clauses);
1095   ASSERT_EQ(Dec.output.size(), 3u);
1096   std::string Dir0 = stringify(Dec.output[0]);
1097   std::string Dir1 = stringify(Dec.output[1]);
1098   std::string Dir2 = stringify(Dec.output[2]);
1099   ASSERT_EQ(Dir0, "target nowait"); // (23)
1100   ASSERT_EQ(Dir1, "parallel");      // (23)
1101   ASSERT_EQ(Dir2, "for");           // (23)
1102 }
1103 
1104 // ---
1105 
1106 // Check that "simd linear(x)" does not fail despite the implied "firstprivate"
1107 // (which "simd" does not allow).
1108 TEST_F(OpenMPDecompositionTest, Misc1) {
1109   omp::Object x{"x"};
1110   omp::List<omp::Clause> Clauses{
1111       {OMPC_linear,
1112        omp::clause::Linear{{std::nullopt, std::nullopt, std::nullopt, {x}}}},
1113   };
1114 
1115   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_simd, Clauses);
1116   ASSERT_EQ(Dec.output.size(), 1u);
1117   std::string Dir0 = stringify(Dec.output[0]);
1118   ASSERT_EQ(Dir0, "simd linear(, , , (x)) lastprivate(, (x))");
1119 }
1120 } // namespace
1121