//===- llvm/unittests/Frontend/OpenMPDecompositionTest.cpp ----------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Frontend/OpenMP/ClauseT.h" #include "llvm/Frontend/OpenMP/ConstructDecompositionT.h" #include "llvm/Frontend/OpenMP/OMP.h" #include "gtest/gtest.h" #include #include #include #include #include #include #include // The actual tests start at comment "--- Test" below. // Create simple instantiations of all clauses to allow manual construction // of clauses, and implement emitting of a directive with clauses to a string. // // The tests then follow the pattern // 1. Create a list of clauses. // 2. Pass them, together with a construct, to the decomposition class. // 3. Extract individual resulting leaf constructs with clauses applied // to them. // 4. Convert them to strings and compare with expected outputs. namespace omp { struct TypeTy {}; // placeholder struct ExprTy {}; // placeholder using IdTy = std::string; } // namespace omp namespace tomp::type { template <> struct ObjectT { const omp::IdTy &id() const { return name; } const std::optional ref() const { return omp::ExprTy{}; } omp::IdTy name; }; } // namespace tomp::type namespace omp { template using List = tomp::type::ListT; using Object = tomp::ObjectT; namespace clause { using DefinedOperator = tomp::type::DefinedOperatorT; using ProcedureDesignator = tomp::type::ProcedureDesignatorT; using ReductionOperator = tomp::type::ReductionIdentifierT; using AcqRel = tomp::clause::AcqRelT; using Acquire = tomp::clause::AcquireT; using AdjustArgs = tomp::clause::AdjustArgsT; using Affinity = tomp::clause::AffinityT; using Aligned = tomp::clause::AlignedT; using Align = tomp::clause::AlignT; using Allocate = tomp::clause::AllocateT; using Allocator = tomp::clause::AllocatorT; using AppendArgs = tomp::clause::AppendArgsT; using AtomicDefaultMemOrder = tomp::clause::AtomicDefaultMemOrderT; using At = tomp::clause::AtT; using Bind = tomp::clause::BindT; using Capture = tomp::clause::CaptureT; using Collapse = tomp::clause::CollapseT; using Compare = tomp::clause::CompareT; using Copyin = tomp::clause::CopyinT; using Copyprivate = tomp::clause::CopyprivateT; using Defaultmap = tomp::clause::DefaultmapT; using Default = tomp::clause::DefaultT; using Depend = tomp::clause::DependT; using Destroy = tomp::clause::DestroyT; using Detach = tomp::clause::DetachT; using Device = tomp::clause::DeviceT; using DeviceType = tomp::clause::DeviceTypeT; using DistSchedule = tomp::clause::DistScheduleT; using Doacross = tomp::clause::DoacrossT; using DynamicAllocators = tomp::clause::DynamicAllocatorsT; using Enter = tomp::clause::EnterT; using Exclusive = tomp::clause::ExclusiveT; using Fail = tomp::clause::FailT; using Filter = tomp::clause::FilterT; using Final = tomp::clause::FinalT; using Firstprivate = tomp::clause::FirstprivateT; using From = tomp::clause::FromT; using Full = tomp::clause::FullT; using Grainsize = tomp::clause::GrainsizeT; using HasDeviceAddr = tomp::clause::HasDeviceAddrT; using Hint = tomp::clause::HintT; using If = tomp::clause::IfT; using Inbranch = tomp::clause::InbranchT; using Inclusive = tomp::clause::InclusiveT; using Indirect = tomp::clause::IndirectT; using Init = tomp::clause::InitT; using InReduction = tomp::clause::InReductionT; using IsDevicePtr = tomp::clause::IsDevicePtrT; using Lastprivate = tomp::clause::LastprivateT; using Linear = tomp::clause::LinearT; using Link = tomp::clause::LinkT; using Map = tomp::clause::MapT; using Match = tomp::clause::MatchT; using Mergeable = tomp::clause::MergeableT; using Message = tomp::clause::MessageT; using Nocontext = tomp::clause::NocontextT; using Nogroup = tomp::clause::NogroupT; using Nontemporal = tomp::clause::NontemporalT; using Notinbranch = tomp::clause::NotinbranchT; using Novariants = tomp::clause::NovariantsT; using Nowait = tomp::clause::NowaitT; using NumTasks = tomp::clause::NumTasksT; using NumTeams = tomp::clause::NumTeamsT; using NumThreads = tomp::clause::NumThreadsT; using OmpxAttribute = tomp::clause::OmpxAttributeT; using OmpxBare = tomp::clause::OmpxBareT; using OmpxDynCgroupMem = tomp::clause::OmpxDynCgroupMemT; using Ordered = tomp::clause::OrderedT; using Order = tomp::clause::OrderT; using Partial = tomp::clause::PartialT; using Priority = tomp::clause::PriorityT; using Private = tomp::clause::PrivateT; using ProcBind = tomp::clause::ProcBindT; using Read = tomp::clause::ReadT; using Reduction = tomp::clause::ReductionT; using Relaxed = tomp::clause::RelaxedT; using Release = tomp::clause::ReleaseT; using ReverseOffload = tomp::clause::ReverseOffloadT; using Safelen = tomp::clause::SafelenT; using Schedule = tomp::clause::ScheduleT; using SeqCst = tomp::clause::SeqCstT; using Severity = tomp::clause::SeverityT; using Shared = tomp::clause::SharedT; using Simdlen = tomp::clause::SimdlenT; using Simd = tomp::clause::SimdT; using Sizes = tomp::clause::SizesT; using TaskReduction = tomp::clause::TaskReductionT; using ThreadLimit = tomp::clause::ThreadLimitT; using Threads = tomp::clause::ThreadsT; using To = tomp::clause::ToT; using UnifiedAddress = tomp::clause::UnifiedAddressT; using UnifiedSharedMemory = tomp::clause::UnifiedSharedMemoryT; using Uniform = tomp::clause::UniformT; using Unknown = tomp::clause::UnknownT; using Untied = tomp::clause::UntiedT; using Update = tomp::clause::UpdateT; using UseDeviceAddr = tomp::clause::UseDeviceAddrT; using UseDevicePtr = tomp::clause::UseDevicePtrT; using UsesAllocators = tomp::clause::UsesAllocatorsT; using Use = tomp::clause::UseT; using Weak = tomp::clause::WeakT; using When = tomp::clause::WhenT; using Write = tomp::clause::WriteT; } // namespace clause struct Helper { std::optional getBaseObject(const Object &object) { return std::nullopt; } std::optional getLoopIterVar() { return std::nullopt; } }; using Clause = tomp::ClauseT; using ConstructDecomposition = tomp::ConstructDecompositionT; using DirectiveWithClauses = tomp::DirectiveWithClauses; } // namespace omp struct StringifyClause { static std::string join(const omp::List &Strings) { std::stringstream Stream; for (const auto &[Index, String] : llvm::enumerate(Strings)) { if (Index != 0) Stream << ", "; Stream << String; } return Stream.str(); } static std::string to_str(llvm::omp::Directive D) { return getOpenMPDirectiveName(D).str(); } static std::string to_str(llvm::omp::Clause C) { return getOpenMPClauseName(C).str(); } static std::string to_str(const omp::TypeTy &Type) { return "type"; } static std::string to_str(const omp::ExprTy &Expr) { return "expr"; } static std::string to_str(const omp::Object &Obj) { return Obj.id(); } template static std::enable_if_t>, std::string> to_str(U &&Item) { return std::to_string(llvm::to_underlying(Item)); } template static std::string to_str(const omp::List &Items) { omp::List Names; llvm::transform(Items, std::back_inserter(Names), [](auto &&S) { return to_str(S); }); return "(" + join(Names) + ")"; } template static std::string to_str(const std::optional &Item) { if (Item) return to_str(*Item); return ""; } template static std::string to_str(const std::tuple &Tuple, std::index_sequence) { omp::List Strings; (Strings.push_back(to_str(std::get(Tuple))), ...); return "(" + join(Strings) + ")"; } template static std::enable_if_t::EmptyTrait::value, std::string> to_str(U &&Item) { return ""; } template static std::enable_if_t::IncompleteTrait::value, std::string> to_str(U &&Item) { return ""; } template static std::enable_if_t::WrapperTrait::value, std::string> to_str(U &&Item) { // For a wrapper, stringify the wrappee, and only add parentheses if // there aren't any already. std::string Str = to_str(Item.v); if (!Str.empty()) { if (Str.front() == '(' && Str.back() == ')') return Str; } return "(" + to_str(Item.v) + ")"; } template static std::enable_if_t::TupleTrait::value, std::string> to_str(U &&Item) { constexpr size_t TupleSize = std::tuple_size_v>; return to_str(Item.t, std::make_index_sequence{}); } template static std::enable_if_t::UnionTrait::value, std::string> to_str(U &&Item) { return std::visit([](auto &&S) { return to_str(S); }, Item.u); } StringifyClause(const omp::Clause &C) // Rely on content stringification to emit enclosing parentheses. : Str(to_str(C.id) + to_str(C)) {} std::string Str; }; std::string stringify(const omp::DirectiveWithClauses &DWC) { std::stringstream Stream; Stream << getOpenMPDirectiveName(DWC.id).str(); for (const omp::Clause &C : DWC.clauses) Stream << ' ' << StringifyClause(C).Str; return Stream.str(); } // --- Tests ---------------------------------------------------------- namespace red { // Make it easier to construct reduction operators from built-in intrinsics. omp::clause::ReductionOperator makeOp(omp::clause::DefinedOperator::IntrinsicOperator Op) { return omp::clause::ReductionOperator{omp::clause::DefinedOperator{Op}}; } } // namespace red namespace { using namespace llvm::omp; class OpenMPDecompositionTest : public testing::Test { protected: void SetUp() override {} void TearDown() override {} omp::Helper Helper; uint32_t AnyVersion = 999; }; // PRIVATE // [5.2:111:5-7] // Directives: distribute, do, for, loop, parallel, scope, sections, simd, // single, target, task, taskloop, teams // // [5.2:340:1-2] // (1) The effect of the 1 private clause is as if it is applied only to the // innermost leaf construct that permits it. TEST_F(OpenMPDecompositionTest, Private1) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_private, omp::clause::Private{{x}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "parallel"); // (1) ASSERT_EQ(Dir1, "sections private(x)"); // (1) } TEST_F(OpenMPDecompositionTest, Private2) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_private, omp::clause::Private{{x}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_masked, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "parallel private(x)"); // (1) ASSERT_EQ(Dir1, "masked"); // (1) } // FIRSTPRIVATE // [5.2:112:5-7] // Directives: distribute, do, for, parallel, scope, sections, single, target, // task, taskloop, teams // // [5.2:340:3-20] // (3) The effect of the firstprivate clause is as if it is applied to one or // more leaf constructs as follows: // (5) To the distribute construct if it is among the constituent constructs; // (6) To the teams construct if it is among the constituent constructs and the // distribute construct is not; // (8) To a worksharing construct that accepts the clause if one is among the // constituent constructs; // (9) To the taskloop construct if it is among the constituent constructs; // (10) To the parallel construct if it is among the constituent constructs and // neither a taskloop construct nor a worksharing construct that accepts // the clause is among them; // (12) To the target construct if it is among the constituent constructs and // the same list item neither appears in a lastprivate clause nor is the // base variable or base pointer of a list item that appears in a map // clause. // // (15) If the parallel construct is among the constituent constructs and the // effect is not as if the firstprivate clause is applied to it by the above // rules, then the effect is as if the shared clause with the same list item is // applied to the parallel construct. // (17) If the teams construct is among the constituent constructs and the // effect is not as if the firstprivate clause is applied to it by the above // rules, then the effect is as if the shared clause with the same list item is // applied to the teams construct. TEST_F(OpenMPDecompositionTest, Firstprivate1) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_firstprivate, omp::clause::Firstprivate{{x}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "parallel shared(x)"); // (10), (15) ASSERT_EQ(Dir1, "sections firstprivate(x)"); // (8) } TEST_F(OpenMPDecompositionTest, Firstprivate2) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_firstprivate, omp::clause::Firstprivate{{x}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_teams_distribute, Clauses); ASSERT_EQ(Dec.output.size(), 3u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); std::string Dir2 = stringify(Dec.output[2]); ASSERT_EQ(Dir0, "target firstprivate(x)"); // (12) ASSERT_EQ(Dir1, "teams shared(x)"); // (6), (17) ASSERT_EQ(Dir2, "distribute firstprivate(x)"); // (5) } TEST_F(OpenMPDecompositionTest, Firstprivate3) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_firstprivate, omp::clause::Firstprivate{{x}}}, {OMPC_lastprivate, omp::clause::Lastprivate{{std::nullopt, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_teams_distribute, Clauses); ASSERT_EQ(Dec.output.size(), 3u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); std::string Dir2 = stringify(Dec.output[2]); ASSERT_EQ(Dir0, "target map(2, , , , (x))"); // (12), (27) ASSERT_EQ(Dir1, "teams shared(x)"); // (6), (17) ASSERT_EQ(Dir2, "distribute firstprivate(x) lastprivate(, (x))"); // (5), (21) } TEST_F(OpenMPDecompositionTest, Firstprivate4) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_firstprivate, omp::clause::Firstprivate{{x}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_teams, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "target firstprivate(x)"); // (12) ASSERT_EQ(Dir1, "teams firstprivate(x)"); // (6) } TEST_F(OpenMPDecompositionTest, Firstprivate5) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_firstprivate, omp::clause::Firstprivate{{x}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_masked_taskloop, Clauses); ASSERT_EQ(Dec.output.size(), 3u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); std::string Dir2 = stringify(Dec.output[2]); ASSERT_EQ(Dir0, "parallel shared(x)"); // (10) ASSERT_EQ(Dir1, "masked"); ASSERT_EQ(Dir2, "taskloop firstprivate(x)"); // (9) } TEST_F(OpenMPDecompositionTest, Firstprivate6) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_firstprivate, omp::clause::Firstprivate{{x}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_masked, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "parallel firstprivate(x)"); // (10) ASSERT_EQ(Dir1, "masked"); } TEST_F(OpenMPDecompositionTest, Firstprivate7) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_firstprivate, omp::clause::Firstprivate{{x}}}, }; // Composite constructs are still decomposed. omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_teams_distribute, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "teams shared(x)"); // (17) ASSERT_EQ(Dir1, "distribute firstprivate(x)"); // (5) } // LASTPRIVATE // [5.2:115:7-8] // Directives: distribute, do, for, loop, sections, simd, taskloop // // [5.2:340:21-30] // (21) The effect of the lastprivate clause is as if it is applied to all leaf // constructs that permit the clause. // (22) If the parallel construct is among the constituent constructs and the // list item is not also specified in the firstprivate clause, then the effect // of the lastprivate clause is as if the shared clause with the same list item // is applied to the parallel construct. // (24) If the teams construct is among the constituent constructs and the list // item is not also specified in the firstprivate clause, then the effect of the // lastprivate clause is as if the shared clause with the same list item is // applied to the teams construct. // (27) If the target construct is among the constituent constructs and the list // item is not the base variable or base pointer of a list item that appears in // a map clause, the effect of the lastprivate clause is as if the same list // item appears in a map clause with a map-type of tofrom. TEST_F(OpenMPDecompositionTest, Lastprivate1) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_lastprivate, omp::clause::Lastprivate{{std::nullopt, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "parallel shared(x)"); // (21), (22) ASSERT_EQ(Dir1, "sections lastprivate(, (x))"); // (21) } TEST_F(OpenMPDecompositionTest, Lastprivate2) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_lastprivate, omp::clause::Lastprivate{{std::nullopt, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_teams_distribute, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "teams shared(x)"); // (21), (25) ASSERT_EQ(Dir1, "distribute lastprivate(, (x))"); // (21) } TEST_F(OpenMPDecompositionTest, Lastprivate3) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_lastprivate, omp::clause::Lastprivate{{std::nullopt, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel_do, Clauses); ASSERT_EQ(Dec.output.size(), 3u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); std::string Dir2 = stringify(Dec.output[2]); ASSERT_EQ(Dir0, "target map(2, , , , (x))"); // (21), (27) ASSERT_EQ(Dir1, "parallel shared(x)"); // (22) ASSERT_EQ(Dir2, "do lastprivate(, (x))"); // (21) } // SHARED // [5.2:110:5-6] // Directives: parallel, task, taskloop, teams // // [5.2:340:31-32] // (31) The effect of the shared, default, thread_limit, or order clause is as // if it is applied to all leaf constructs that permit the clause. TEST_F(OpenMPDecompositionTest, Shared1) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_shared, omp::clause::Shared{{x}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_masked_taskloop, Clauses); ASSERT_EQ(Dec.output.size(), 3u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); std::string Dir2 = stringify(Dec.output[2]); ASSERT_EQ(Dir0, "parallel shared(x)"); // (31) ASSERT_EQ(Dir1, "masked"); // (31) ASSERT_EQ(Dir2, "taskloop shared(x)"); // (31) } // DEFAULT // [5.2:109:5-6] // Directives: parallel, task, taskloop, teams // // [5.2:340:31-32] // (31) The effect of the shared, default, thread_limit, or order clause is as // if it is applied to all leaf constructs that permit the clause. TEST_F(OpenMPDecompositionTest, Default1) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_default, omp::clause::Default{ omp::clause::Default::DataSharingAttribute::Firstprivate}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_masked_taskloop, Clauses); ASSERT_EQ(Dec.output.size(), 3u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); std::string Dir2 = stringify(Dec.output[2]); ASSERT_EQ(Dir0, "parallel default(0)"); // (31) ASSERT_EQ(Dir1, "masked"); // (31) ASSERT_EQ(Dir2, "taskloop default(0)"); // (31) } // THREAD_LIMIT // [5.2:277:14-15] // Directives: target, teams // // [5.2:340:31-32] // (31) The effect of the shared, default, thread_limit, or order clause is as // if it is applied to all leaf constructs that permit the clause. TEST_F(OpenMPDecompositionTest, ThreadLimit1) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_thread_limit, omp::clause::ThreadLimit{omp::ExprTy{}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_teams_distribute, Clauses); ASSERT_EQ(Dec.output.size(), 3u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); std::string Dir2 = stringify(Dec.output[2]); ASSERT_EQ(Dir0, "target thread_limit(expr)"); // (31) ASSERT_EQ(Dir1, "teams thread_limit(expr)"); // (31) ASSERT_EQ(Dir2, "distribute"); // (31) } // ORDER // [5.2:234:3-4] // Directives: distribute, do, for, loop, simd // // [5.2:340:31-32] // (31) The effect of the shared, default, thread_limit, or order clause is as // if it is applied to all leaf constructs that permit the clause. TEST_F(OpenMPDecompositionTest, Order1) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_order, omp::clause::Order{{omp::clause::Order::OrderModifier::Unconstrained, omp::clause::Order::Ordering::Concurrent}}}, }; omp::ConstructDecomposition Dec( AnyVersion, Helper, OMPD_target_teams_distribute_parallel_for_simd, Clauses); ASSERT_EQ(Dec.output.size(), 6u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); std::string Dir2 = stringify(Dec.output[2]); std::string Dir3 = stringify(Dec.output[3]); std::string Dir4 = stringify(Dec.output[4]); std::string Dir5 = stringify(Dec.output[5]); ASSERT_EQ(Dir0, "target"); // (31) ASSERT_EQ(Dir1, "teams"); // (31) ASSERT_EQ(Dir2, "distribute order(1, 0)"); // (31) ASSERT_EQ(Dir3, "parallel"); // (31) ASSERT_EQ(Dir4, "for order(1, 0)"); // (31) ASSERT_EQ(Dir5, "simd order(1, 0)"); // (31) } // ALLOCATE // [5.2:178:7-9] // Directives: allocators, distribute, do, for, parallel, scope, sections, // single, target, task, taskgroup, taskloop, teams // // [5.2:340:33-35] // (33) The effect of the allocate clause is as if it is applied to all leaf // constructs that permit the clause and to which a data-sharing attribute // clause that may create a private copy of the same list item is applied. TEST_F(OpenMPDecompositionTest, Allocate1) { omp::Object x{"x"}; // Allocate + firstprivate omp::List Clauses{ {OMPC_allocate, omp::clause::Allocate{{std::nullopt, std::nullopt, {x}}}}, {OMPC_firstprivate, omp::clause::Firstprivate{{x}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "parallel shared(x)"); // (33) ASSERT_EQ(Dir1, "sections firstprivate(x) allocate(, , (x))"); // (33) } TEST_F(OpenMPDecompositionTest, Allocate2) { omp::Object x{"x"}; auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add); // Allocate + in_reduction omp::List Clauses{ {OMPC_allocate, omp::clause::Allocate{{std::nullopt, std::nullopt, {x}}}}, {OMPC_in_reduction, omp::clause::InReduction{{{Add}, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "target in_reduction((3), (x)) allocate(, , (x))"); // (33) ASSERT_EQ(Dir1, "parallel"); // (33) } TEST_F(OpenMPDecompositionTest, Allocate3) { omp::Object x{"x"}; // Allocate + linear omp::List Clauses{ {OMPC_allocate, omp::clause::Allocate{{std::nullopt, std::nullopt, {x}}}}, {OMPC_linear, omp::clause::Linear{{std::nullopt, std::nullopt, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_for, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); // The "shared" clause is duplicated---this isn't harmful, but it // should be fixed eventually. ASSERT_EQ(Dir0, "parallel shared(x) shared(x)"); // (33) ASSERT_EQ(Dir1, "for linear(, , (x)) firstprivate(x) lastprivate(, (x)) " "allocate(, , (x))"); // (33) } TEST_F(OpenMPDecompositionTest, Allocate4) { omp::Object x{"x"}; // Allocate + lastprivate omp::List Clauses{ {OMPC_allocate, omp::clause::Allocate{{std::nullopt, std::nullopt, {x}}}}, {OMPC_lastprivate, omp::clause::Lastprivate{{std::nullopt, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "parallel shared(x)"); // (33) ASSERT_EQ(Dir1, "sections lastprivate(, (x)) allocate(, , (x))"); // (33) } TEST_F(OpenMPDecompositionTest, Allocate5) { omp::Object x{"x"}; // Allocate + private omp::List Clauses{ {OMPC_allocate, omp::clause::Allocate{{std::nullopt, std::nullopt, {x}}}}, {OMPC_private, omp::clause::Private{{x}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "parallel"); // (33) ASSERT_EQ(Dir1, "sections private(x) allocate(, , (x))"); // (33) } TEST_F(OpenMPDecompositionTest, Allocate6) { omp::Object x{"x"}; auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add); // Allocate + reduction omp::List Clauses{ {OMPC_allocate, omp::clause::Allocate{{std::nullopt, std::nullopt, {x}}}}, {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "parallel shared(x)"); // (33) ASSERT_EQ(Dir1, "sections reduction(, (3), (x)) allocate(, , (x))"); // (33) } // REDUCTION // [5.2:134:17-18] // Directives: do, for, loop, parallel, scope, sections, simd, taskloop, teams // // [5.2:340-341:36-13] // (36) The effect of the reduction clause is as if it is applied to all leaf // constructs that permit the clause, except for the following constructs: // (1) The parallel construct, when combined with the sections, // worksharing-loop, loop, or taskloop construct; and // (3) The teams construct, when combined with the loop construct. // (4) For the parallel and teams constructs above, the effect of the reduction // clause instead is as if each list item or, for any list item that is an array // item, its corresponding base array or base pointer appears in a shared clause // for the construct. // (6) If the task reduction-modifier is specified, the effect is as if it only // modifies the behavior of the reduction clause on the innermost leaf construct // that accepts the modifier (see Section 5.5.8). // (8) If the inscan reduction-modifier is specified, the effect is as if it // modifies the behavior of the reduction clause on all constructs of the // combined construct to which the clause is applied and that accept the // modifier. // (10) If a list item in a reduction clause on a combined target construct does // not have the same base variable or base pointer as a list item in a map // clause on the construct, then the effect is as if the list item in the // reduction clause appears as a list item in a map clause with a map-type of // tofrom. TEST_F(OpenMPDecompositionTest, Reduction1) { omp::Object x{"x"}; auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add); omp::List Clauses{ {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "parallel shared(x)"); // (36), (1), (4) ASSERT_EQ(Dir1, "sections reduction(, (3), (x))"); // (36) } TEST_F(OpenMPDecompositionTest, Reduction2) { omp::Object x{"x"}; auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add); omp::List Clauses{ {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_masked, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "parallel reduction(, (3), (x))"); // (36), (1), (4) ASSERT_EQ(Dir1, "masked"); // (36) } TEST_F(OpenMPDecompositionTest, Reduction3) { omp::Object x{"x"}; auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add); omp::List Clauses{ {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_teams_loop, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "teams shared(x)"); // (36), (3), (4) ASSERT_EQ(Dir1, "loop reduction(, (3), (x))"); // (36) } TEST_F(OpenMPDecompositionTest, Reduction4) { omp::Object x{"x"}; auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add); omp::List Clauses{ {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_teams_distribute_parallel_for, Clauses); ASSERT_EQ(Dec.output.size(), 4u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); std::string Dir2 = stringify(Dec.output[2]); std::string Dir3 = stringify(Dec.output[3]); ASSERT_EQ(Dir0, "teams reduction(, (3), (x))"); // (36), (3) ASSERT_EQ(Dir1, "distribute"); // (36) ASSERT_EQ(Dir2, "parallel shared(x)"); // (36), (1), (4) ASSERT_EQ(Dir3, "for reduction(, (3), (x))"); // (36) } TEST_F(OpenMPDecompositionTest, Reduction5) { omp::Object x{"x"}; auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add); auto TaskMod = omp::clause::Reduction::ReductionModifier::Task; omp::List Clauses{ {OMPC_reduction, omp::clause::Reduction{{TaskMod, {Add}, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_teams_distribute_parallel_for, Clauses); ASSERT_EQ(Dec.output.size(), 4u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); std::string Dir2 = stringify(Dec.output[2]); std::string Dir3 = stringify(Dec.output[3]); ASSERT_EQ(Dir0, "teams reduction(, (3), (x))"); // (36), (3), (6) ASSERT_EQ(Dir1, "distribute"); // (36) ASSERT_EQ(Dir2, "parallel shared(x)"); // (36), (1), (4) ASSERT_EQ(Dir3, "for reduction(2, (3), (x))"); // (36), (6) } TEST_F(OpenMPDecompositionTest, Reduction6) { omp::Object x{"x"}; auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add); auto InscanMod = omp::clause::Reduction::ReductionModifier::Inscan; omp::List Clauses{ {OMPC_reduction, omp::clause::Reduction{{InscanMod, {Add}, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_teams_distribute_parallel_for, Clauses); ASSERT_EQ(Dec.output.size(), 4u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); std::string Dir2 = stringify(Dec.output[2]); std::string Dir3 = stringify(Dec.output[3]); ASSERT_EQ(Dir0, "teams reduction(, (3), (x))"); // (36), (3), (8) ASSERT_EQ(Dir1, "distribute"); // (36) ASSERT_EQ(Dir2, "parallel shared(x)"); // (36), (1), (4) ASSERT_EQ(Dir3, "for reduction(1, (3), (x))"); // (36), (8) } TEST_F(OpenMPDecompositionTest, Reduction7) { omp::Object x{"x"}; auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add); omp::List Clauses{ {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel_do, Clauses); ASSERT_EQ(Dec.output.size(), 3u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); std::string Dir2 = stringify(Dec.output[2]); ASSERT_EQ(Dir0, "target map(2, , , , (x))"); // (36), (10) ASSERT_EQ(Dir1, "parallel shared(x)"); // (36), (1), (4) ASSERT_EQ(Dir2, "do reduction(, (3), (x))"); // (36) } // IF // [5.2:72:7-9] // Directives: cancel, parallel, simd, target, target data, target enter data, // target exit data, target update, task, taskloop // // [5.2:72:15-18] // (15) For combined or composite constructs, the if clause only applies to the // semantics of the construct named in the directive-name-modifier. // (16) For a combined or composite construct, if no directive-name-modifier is // specified then the if clause applies to all constituent constructs to which // an if clause can apply. TEST_F(OpenMPDecompositionTest, If1) { omp::List Clauses{ {OMPC_if, omp::clause::If{{llvm::omp::Directive::OMPD_parallel, omp::ExprTy{}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel_for_simd, Clauses); ASSERT_EQ(Dec.output.size(), 4u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); std::string Dir2 = stringify(Dec.output[2]); std::string Dir3 = stringify(Dec.output[3]); ASSERT_EQ(Dir0, "target"); // (15) ASSERT_EQ(Dir1, "parallel if(, expr)"); // (15) ASSERT_EQ(Dir2, "for"); // (15) ASSERT_EQ(Dir3, "simd"); // (15) } TEST_F(OpenMPDecompositionTest, If2) { omp::List Clauses{ {OMPC_if, omp::clause::If{{std::nullopt, omp::ExprTy{}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel_for_simd, Clauses); ASSERT_EQ(Dec.output.size(), 4u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); std::string Dir2 = stringify(Dec.output[2]); std::string Dir3 = stringify(Dec.output[3]); ASSERT_EQ(Dir0, "target if(, expr)"); // (16) ASSERT_EQ(Dir1, "parallel if(, expr)"); // (16) ASSERT_EQ(Dir2, "for"); // (16) ASSERT_EQ(Dir3, "simd if(, expr)"); // (16) } // LINEAR // [5.2:118:1-2] // Directives: declare simd, do, for, simd // // [5.2:341:15-22] // (15.1) The effect of the linear clause is as if it is applied to the // innermost leaf construct. // (15.2) Additionally, if the list item is not the iteration variable of a simd // or worksharing-loop SIMD construct, the effect on the outer leaf constructs // is as if the list item was specified in firstprivate and lastprivate clauses // on the combined or composite construct, with the rules specified above // applied. // (19) If a list item of the linear clause is the iteration variable of a simd // or worksharing-loop SIMD construct and it is not declared in the construct, // the effect on the outer leaf constructs is as if the list item was specified // in a lastprivate clause on the combined or composite construct with the rules // specified above applied. TEST_F(OpenMPDecompositionTest, Linear1) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_linear, omp::clause::Linear{{std::nullopt, std::nullopt, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_for_simd, Clauses); ASSERT_EQ(Dec.output.size(), 2u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); ASSERT_EQ(Dir0, "for firstprivate(x) lastprivate(, (x))"); // (15.1), (15.2) ASSERT_EQ(Dir1, "simd linear(, , (x)) lastprivate(, (x))"); // (15.1) } // NOWAIT // [5.2:308:11-13] // Directives: dispatch, do, for, interop, scope, sections, single, target, // target enter data, target exit data, target update, taskwait, workshare // // [5.2:341:23] // (23) The effect of the nowait clause is as if it is applied to the outermost // leaf construct that permits it. TEST_F(OpenMPDecompositionTest, Nowait1) { omp::List Clauses{ {OMPC_nowait, omp::clause::Nowait{}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel_for, Clauses); ASSERT_EQ(Dec.output.size(), 3u); std::string Dir0 = stringify(Dec.output[0]); std::string Dir1 = stringify(Dec.output[1]); std::string Dir2 = stringify(Dec.output[2]); ASSERT_EQ(Dir0, "target nowait"); // (23) ASSERT_EQ(Dir1, "parallel"); // (23) ASSERT_EQ(Dir2, "for"); // (23) } // --- // Check that "simd linear(x)" does not fail despite the implied "firstprivate" // (which "simd" does not allow). TEST_F(OpenMPDecompositionTest, Misc1) { omp::Object x{"x"}; omp::List Clauses{ {OMPC_linear, omp::clause::Linear{{std::nullopt, std::nullopt, {x}}}}, }; omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_simd, Clauses); ASSERT_EQ(Dec.output.size(), 1u); std::string Dir0 = stringify(Dec.output[0]); ASSERT_EQ(Dir0, "simd linear(, , (x)) lastprivate(, (x))"); } } // namespace