xref: /llvm-project/llvm/unittests/Frontend/OpenMPDecompositionTest.cpp (revision 03cbe42627c7a7940b47cc1a2cda0120bc9c6d5e)
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, omp::clause::Allocate{{std::nullopt, std::nullopt, {x}}}},
712       {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
713   };
714 
715   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
716                                   Clauses);
717   ASSERT_EQ(Dec.output.size(), 2u);
718 
719   std::string Dir0 = stringify(Dec.output[0]);
720   std::string Dir1 = stringify(Dec.output[1]);
721   ASSERT_EQ(Dir0, "parallel shared(x)");                         // (33)
722   ASSERT_EQ(Dir1, "sections firstprivate(x) allocate(, , (x))"); // (33)
723 }
724 
725 TEST_F(OpenMPDecompositionTest, Allocate2) {
726   omp::Object x{"x"};
727   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
728 
729   // Allocate + in_reduction
730   omp::List<omp::Clause> Clauses{
731       {OMPC_allocate, omp::clause::Allocate{{std::nullopt, std::nullopt, {x}}}},
732       {OMPC_in_reduction, omp::clause::InReduction{{{Add}, {x}}}},
733   };
734 
735   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel,
736                                   Clauses);
737   ASSERT_EQ(Dec.output.size(), 2u);
738 
739   std::string Dir0 = stringify(Dec.output[0]);
740   std::string Dir1 = stringify(Dec.output[1]);
741   ASSERT_EQ(Dir0, "target in_reduction((3), (x)) allocate(, , (x))"); // (33)
742   ASSERT_EQ(Dir1, "parallel");                                        // (33)
743 }
744 
745 TEST_F(OpenMPDecompositionTest, Allocate3) {
746   omp::Object x{"x"};
747 
748   // Allocate + linear
749   omp::List<omp::Clause> Clauses{
750       {OMPC_allocate, omp::clause::Allocate{{std::nullopt, std::nullopt, {x}}}},
751       {OMPC_linear, omp::clause::Linear{{std::nullopt, std::nullopt, {x}}}},
752   };
753 
754   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_for,
755                                   Clauses);
756   ASSERT_EQ(Dec.output.size(), 2u);
757 
758   std::string Dir0 = stringify(Dec.output[0]);
759   std::string Dir1 = stringify(Dec.output[1]);
760   // The "shared" clause is duplicated---this isn't harmful, but it
761   // should be fixed eventually.
762   ASSERT_EQ(Dir0, "parallel shared(x) shared(x)"); // (33)
763   ASSERT_EQ(Dir1, "for linear(, , (x)) firstprivate(x) lastprivate(, (x)) "
764                   "allocate(, , (x))"); // (33)
765 }
766 
767 TEST_F(OpenMPDecompositionTest, Allocate4) {
768   omp::Object x{"x"};
769 
770   // Allocate + lastprivate
771   omp::List<omp::Clause> Clauses{
772       {OMPC_allocate, omp::clause::Allocate{{std::nullopt, std::nullopt, {x}}}},
773       {OMPC_lastprivate, omp::clause::Lastprivate{{std::nullopt, {x}}}},
774   };
775 
776   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
777                                   Clauses);
778   ASSERT_EQ(Dec.output.size(), 2u);
779 
780   std::string Dir0 = stringify(Dec.output[0]);
781   std::string Dir1 = stringify(Dec.output[1]);
782   ASSERT_EQ(Dir0, "parallel shared(x)");                            // (33)
783   ASSERT_EQ(Dir1, "sections lastprivate(, (x)) allocate(, , (x))"); // (33)
784 }
785 
786 TEST_F(OpenMPDecompositionTest, Allocate5) {
787   omp::Object x{"x"};
788 
789   // Allocate + private
790   omp::List<omp::Clause> Clauses{
791       {OMPC_allocate, omp::clause::Allocate{{std::nullopt, std::nullopt, {x}}}},
792       {OMPC_private, omp::clause::Private{{x}}},
793   };
794 
795   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
796                                   Clauses);
797   ASSERT_EQ(Dec.output.size(), 2u);
798 
799   std::string Dir0 = stringify(Dec.output[0]);
800   std::string Dir1 = stringify(Dec.output[1]);
801   ASSERT_EQ(Dir0, "parallel");                              // (33)
802   ASSERT_EQ(Dir1, "sections private(x) allocate(, , (x))"); // (33)
803 }
804 
805 TEST_F(OpenMPDecompositionTest, Allocate6) {
806   omp::Object x{"x"};
807   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
808 
809   // Allocate + reduction
810   omp::List<omp::Clause> Clauses{
811       {OMPC_allocate, omp::clause::Allocate{{std::nullopt, std::nullopt, {x}}}},
812       {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
813   };
814 
815   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
816                                   Clauses);
817   ASSERT_EQ(Dec.output.size(), 2u);
818 
819   std::string Dir0 = stringify(Dec.output[0]);
820   std::string Dir1 = stringify(Dec.output[1]);
821   ASSERT_EQ(Dir0, "parallel shared(x)");                               // (33)
822   ASSERT_EQ(Dir1, "sections reduction(, (3), (x)) allocate(, , (x))"); // (33)
823 }
824 
825 // REDUCTION
826 // [5.2:134:17-18]
827 // Directives: do, for, loop, parallel, scope, sections, simd, taskloop, teams
828 //
829 // [5.2:340-341:36-13]
830 // (36) The effect of the reduction clause is as if it is applied to all leaf
831 // constructs that permit the clause, except for the following constructs:
832 //  (1) The parallel construct, when combined with the sections,
833 //      worksharing-loop, loop, or taskloop construct; and
834 //  (3) The teams construct, when combined with the loop construct.
835 // (4) For the parallel and teams constructs above, the effect of the reduction
836 // clause instead is as if each list item or, for any list item that is an array
837 // item, its corresponding base array or base pointer appears in a shared clause
838 // for the construct.
839 // (6) If the task reduction-modifier is specified, the effect is as if it only
840 // modifies the behavior of the reduction clause on the innermost leaf construct
841 // that accepts the modifier (see Section 5.5.8).
842 // (8) If the inscan reduction-modifier is specified, the effect is as if it
843 // modifies the behavior of the reduction clause on all constructs of the
844 // combined construct to which the clause is applied and that accept the
845 // modifier.
846 // (10) If a list item in a reduction clause on a combined target construct does
847 // not have the same base variable or base pointer as a list item in a map
848 // clause on the construct, then the effect is as if the list item in the
849 // reduction clause appears as a list item in a map clause with a map-type of
850 // tofrom.
851 TEST_F(OpenMPDecompositionTest, Reduction1) {
852   omp::Object x{"x"};
853   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
854 
855   omp::List<omp::Clause> Clauses{
856       {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
857   };
858 
859   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
860                                   Clauses);
861   ASSERT_EQ(Dec.output.size(), 2u);
862 
863   std::string Dir0 = stringify(Dec.output[0]);
864   std::string Dir1 = stringify(Dec.output[1]);
865   ASSERT_EQ(Dir0, "parallel shared(x)");             // (36), (1), (4)
866   ASSERT_EQ(Dir1, "sections reduction(, (3), (x))"); // (36)
867 }
868 
869 TEST_F(OpenMPDecompositionTest, Reduction2) {
870   omp::Object x{"x"};
871   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
872 
873   omp::List<omp::Clause> Clauses{
874       {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
875   };
876 
877   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_masked,
878                                   Clauses);
879   ASSERT_EQ(Dec.output.size(), 2u);
880 
881   std::string Dir0 = stringify(Dec.output[0]);
882   std::string Dir1 = stringify(Dec.output[1]);
883   ASSERT_EQ(Dir0, "parallel reduction(, (3), (x))"); // (36), (1), (4)
884   ASSERT_EQ(Dir1, "masked");                         // (36)
885 }
886 
887 TEST_F(OpenMPDecompositionTest, Reduction3) {
888   omp::Object x{"x"};
889   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
890 
891   omp::List<omp::Clause> Clauses{
892       {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
893   };
894 
895   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_teams_loop, Clauses);
896   ASSERT_EQ(Dec.output.size(), 2u);
897 
898   std::string Dir0 = stringify(Dec.output[0]);
899   std::string Dir1 = stringify(Dec.output[1]);
900   ASSERT_EQ(Dir0, "teams shared(x)");            // (36), (3), (4)
901   ASSERT_EQ(Dir1, "loop reduction(, (3), (x))"); // (36)
902 }
903 
904 TEST_F(OpenMPDecompositionTest, Reduction4) {
905   omp::Object x{"x"};
906   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
907 
908   omp::List<omp::Clause> Clauses{
909       {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
910   };
911 
912   omp::ConstructDecomposition Dec(AnyVersion, Helper,
913                                   OMPD_teams_distribute_parallel_for, Clauses);
914   ASSERT_EQ(Dec.output.size(), 4u);
915 
916   std::string Dir0 = stringify(Dec.output[0]);
917   std::string Dir1 = stringify(Dec.output[1]);
918   std::string Dir2 = stringify(Dec.output[2]);
919   std::string Dir3 = stringify(Dec.output[3]);
920   ASSERT_EQ(Dir0, "teams reduction(, (3), (x))"); // (36), (3)
921   ASSERT_EQ(Dir1, "distribute");                  // (36)
922   ASSERT_EQ(Dir2, "parallel shared(x)");          // (36), (1), (4)
923   ASSERT_EQ(Dir3, "for reduction(, (3), (x))");   // (36)
924 }
925 
926 TEST_F(OpenMPDecompositionTest, Reduction5) {
927   omp::Object x{"x"};
928   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
929   auto TaskMod = omp::clause::Reduction::ReductionModifier::Task;
930 
931   omp::List<omp::Clause> Clauses{
932       {OMPC_reduction, omp::clause::Reduction{{TaskMod, {Add}, {x}}}},
933   };
934 
935   omp::ConstructDecomposition Dec(AnyVersion, Helper,
936                                   OMPD_teams_distribute_parallel_for, Clauses);
937   ASSERT_EQ(Dec.output.size(), 4u);
938 
939   std::string Dir0 = stringify(Dec.output[0]);
940   std::string Dir1 = stringify(Dec.output[1]);
941   std::string Dir2 = stringify(Dec.output[2]);
942   std::string Dir3 = stringify(Dec.output[3]);
943   ASSERT_EQ(Dir0, "teams reduction(, (3), (x))"); // (36), (3), (6)
944   ASSERT_EQ(Dir1, "distribute");                  // (36)
945   ASSERT_EQ(Dir2, "parallel shared(x)");          // (36), (1), (4)
946   ASSERT_EQ(Dir3, "for reduction(2, (3), (x))");  // (36), (6)
947 }
948 
949 TEST_F(OpenMPDecompositionTest, Reduction6) {
950   omp::Object x{"x"};
951   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
952   auto InscanMod = omp::clause::Reduction::ReductionModifier::Inscan;
953 
954   omp::List<omp::Clause> Clauses{
955       {OMPC_reduction, omp::clause::Reduction{{InscanMod, {Add}, {x}}}},
956   };
957 
958   omp::ConstructDecomposition Dec(AnyVersion, Helper,
959                                   OMPD_teams_distribute_parallel_for, Clauses);
960   ASSERT_EQ(Dec.output.size(), 4u);
961 
962   std::string Dir0 = stringify(Dec.output[0]);
963   std::string Dir1 = stringify(Dec.output[1]);
964   std::string Dir2 = stringify(Dec.output[2]);
965   std::string Dir3 = stringify(Dec.output[3]);
966   ASSERT_EQ(Dir0, "teams reduction(, (3), (x))"); // (36), (3), (8)
967   ASSERT_EQ(Dir1, "distribute");                  // (36)
968   ASSERT_EQ(Dir2, "parallel shared(x)");          // (36), (1), (4)
969   ASSERT_EQ(Dir3, "for reduction(1, (3), (x))");  // (36), (8)
970 }
971 
972 TEST_F(OpenMPDecompositionTest, Reduction7) {
973   omp::Object x{"x"};
974   auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
975 
976   omp::List<omp::Clause> Clauses{
977       {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
978   };
979 
980   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel_do,
981                                   Clauses);
982   ASSERT_EQ(Dec.output.size(), 3u);
983 
984   std::string Dir0 = stringify(Dec.output[0]);
985   std::string Dir1 = stringify(Dec.output[1]);
986   std::string Dir2 = stringify(Dec.output[2]);
987   ASSERT_EQ(Dir0, "target map(2, , , , (x))"); // (36), (10)
988   ASSERT_EQ(Dir1, "parallel shared(x)");       // (36), (1), (4)
989   ASSERT_EQ(Dir2, "do reduction(, (3), (x))"); // (36)
990 }
991 
992 // IF
993 // [5.2:72:7-9]
994 // Directives: cancel, parallel, simd, target, target data, target enter data,
995 // target exit data, target update, task, taskloop
996 //
997 // [5.2:72:15-18]
998 // (15) For combined or composite constructs, the if clause only applies to the
999 // semantics of the construct named in the directive-name-modifier.
1000 // (16) For a combined or composite construct, if no directive-name-modifier is
1001 // specified then the if clause applies to all constituent constructs to which
1002 // an if clause can apply.
1003 TEST_F(OpenMPDecompositionTest, If1) {
1004   omp::List<omp::Clause> Clauses{
1005       {OMPC_if,
1006        omp::clause::If{{llvm::omp::Directive::OMPD_parallel, omp::ExprTy{}}}},
1007   };
1008 
1009   omp::ConstructDecomposition Dec(AnyVersion, Helper,
1010                                   OMPD_target_parallel_for_simd, Clauses);
1011   ASSERT_EQ(Dec.output.size(), 4u);
1012   std::string Dir0 = stringify(Dec.output[0]);
1013   std::string Dir1 = stringify(Dec.output[1]);
1014   std::string Dir2 = stringify(Dec.output[2]);
1015   std::string Dir3 = stringify(Dec.output[3]);
1016   ASSERT_EQ(Dir0, "target");              // (15)
1017   ASSERT_EQ(Dir1, "parallel if(, expr)"); // (15)
1018   ASSERT_EQ(Dir2, "for");                 // (15)
1019   ASSERT_EQ(Dir3, "simd");                // (15)
1020 }
1021 
1022 TEST_F(OpenMPDecompositionTest, If2) {
1023   omp::List<omp::Clause> Clauses{
1024       {OMPC_if, omp::clause::If{{std::nullopt, omp::ExprTy{}}}},
1025   };
1026 
1027   omp::ConstructDecomposition Dec(AnyVersion, Helper,
1028                                   OMPD_target_parallel_for_simd, Clauses);
1029   ASSERT_EQ(Dec.output.size(), 4u);
1030   std::string Dir0 = stringify(Dec.output[0]);
1031   std::string Dir1 = stringify(Dec.output[1]);
1032   std::string Dir2 = stringify(Dec.output[2]);
1033   std::string Dir3 = stringify(Dec.output[3]);
1034   ASSERT_EQ(Dir0, "target if(, expr)");   // (16)
1035   ASSERT_EQ(Dir1, "parallel if(, expr)"); // (16)
1036   ASSERT_EQ(Dir2, "for");                 // (16)
1037   ASSERT_EQ(Dir3, "simd if(, expr)");     // (16)
1038 }
1039 
1040 // LINEAR
1041 // [5.2:118:1-2]
1042 // Directives: declare simd, do, for, simd
1043 //
1044 // [5.2:341:15-22]
1045 // (15.1) The effect of the linear clause is as if it is applied to the
1046 // innermost leaf construct.
1047 // (15.2) Additionally, if the list item is not the iteration variable of a simd
1048 // or worksharing-loop SIMD construct, the effect on the outer leaf constructs
1049 // is as if the list item was specified in firstprivate and lastprivate clauses
1050 // on the combined or composite construct, with the rules specified above
1051 // applied.
1052 // (19) If a list item of the linear clause is the iteration variable of a simd
1053 // or worksharing-loop SIMD construct and it is not declared in the construct,
1054 // the effect on the outer leaf constructs is as if the list item was specified
1055 // in a lastprivate clause on the combined or composite construct with the rules
1056 // specified above applied.
1057 TEST_F(OpenMPDecompositionTest, Linear1) {
1058   omp::Object x{"x"};
1059 
1060   omp::List<omp::Clause> Clauses{
1061       {OMPC_linear, omp::clause::Linear{{std::nullopt, std::nullopt, {x}}}},
1062   };
1063 
1064   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_for_simd, Clauses);
1065   ASSERT_EQ(Dec.output.size(), 2u);
1066   std::string Dir0 = stringify(Dec.output[0]);
1067   std::string Dir1 = stringify(Dec.output[1]);
1068   ASSERT_EQ(Dir0, "for firstprivate(x) lastprivate(, (x))"); // (15.1), (15.2)
1069   ASSERT_EQ(Dir1, "simd linear(, , (x)) lastprivate(, (x))"); // (15.1)
1070 }
1071 
1072 // NOWAIT
1073 // [5.2:308:11-13]
1074 // Directives: dispatch, do, for, interop, scope, sections, single, target,
1075 // target enter data, target exit data, target update, taskwait, workshare
1076 //
1077 // [5.2:341:23]
1078 // (23) The effect of the nowait clause is as if it is applied to the outermost
1079 // leaf construct that permits it.
1080 TEST_F(OpenMPDecompositionTest, Nowait1) {
1081   omp::List<omp::Clause> Clauses{
1082       {OMPC_nowait, omp::clause::Nowait{}},
1083   };
1084 
1085   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel_for,
1086                                   Clauses);
1087   ASSERT_EQ(Dec.output.size(), 3u);
1088   std::string Dir0 = stringify(Dec.output[0]);
1089   std::string Dir1 = stringify(Dec.output[1]);
1090   std::string Dir2 = stringify(Dec.output[2]);
1091   ASSERT_EQ(Dir0, "target nowait"); // (23)
1092   ASSERT_EQ(Dir1, "parallel");      // (23)
1093   ASSERT_EQ(Dir2, "for");           // (23)
1094 }
1095 
1096 // ---
1097 
1098 // Check that "simd linear(x)" does not fail despite the implied "firstprivate"
1099 // (which "simd" does not allow).
1100 TEST_F(OpenMPDecompositionTest, Misc1) {
1101   omp::Object x{"x"};
1102   omp::List<omp::Clause> Clauses{
1103       {OMPC_linear, omp::clause::Linear{{std::nullopt, std::nullopt, {x}}}},
1104   };
1105 
1106   omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_simd, Clauses);
1107   ASSERT_EQ(Dec.output.size(), 1u);
1108   std::string Dir0 = stringify(Dec.output[0]);
1109   ASSERT_EQ(Dir0, "simd linear(, , (x)) lastprivate(, (x))");
1110 }
1111 } // namespace
1112