blob: c70341b5a86d24d8c8faaab84c10402565e5d5ce [file] [log] [blame] [edit]
//===- 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 <iterator>
#include <optional>
#include <sstream>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
// 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<omp::IdTy, omp::ExprTy> {
const omp::IdTy &id() const { return name; }
const std::optional<omp::ExprTy> ref() const { return omp::ExprTy{}; }
omp::IdTy name;
};
} // namespace tomp::type
namespace omp {
template <typename ElemTy> using List = tomp::type::ListT<ElemTy>;
using Object = tomp::ObjectT<IdTy, ExprTy>;
namespace clause {
using DefinedOperator = tomp::type::DefinedOperatorT<IdTy, ExprTy>;
using ProcedureDesignator = tomp::type::ProcedureDesignatorT<IdTy, ExprTy>;
using ReductionOperator = tomp::type::ReductionIdentifierT<IdTy, ExprTy>;
using AcqRel = tomp::clause::AcqRelT<TypeTy, IdTy, ExprTy>;
using Acquire = tomp::clause::AcquireT<TypeTy, IdTy, ExprTy>;
using AdjustArgs = tomp::clause::AdjustArgsT<TypeTy, IdTy, ExprTy>;
using Affinity = tomp::clause::AffinityT<TypeTy, IdTy, ExprTy>;
using Aligned = tomp::clause::AlignedT<TypeTy, IdTy, ExprTy>;
using Align = tomp::clause::AlignT<TypeTy, IdTy, ExprTy>;
using Allocate = tomp::clause::AllocateT<TypeTy, IdTy, ExprTy>;
using Allocator = tomp::clause::AllocatorT<TypeTy, IdTy, ExprTy>;
using AppendArgs = tomp::clause::AppendArgsT<TypeTy, IdTy, ExprTy>;
using AtomicDefaultMemOrder =
tomp::clause::AtomicDefaultMemOrderT<TypeTy, IdTy, ExprTy>;
using At = tomp::clause::AtT<TypeTy, IdTy, ExprTy>;
using Bind = tomp::clause::BindT<TypeTy, IdTy, ExprTy>;
using Capture = tomp::clause::CaptureT<TypeTy, IdTy, ExprTy>;
using Collapse = tomp::clause::CollapseT<TypeTy, IdTy, ExprTy>;
using Compare = tomp::clause::CompareT<TypeTy, IdTy, ExprTy>;
using Copyin = tomp::clause::CopyinT<TypeTy, IdTy, ExprTy>;
using Copyprivate = tomp::clause::CopyprivateT<TypeTy, IdTy, ExprTy>;
using Defaultmap = tomp::clause::DefaultmapT<TypeTy, IdTy, ExprTy>;
using Default = tomp::clause::DefaultT<TypeTy, IdTy, ExprTy>;
using Depend = tomp::clause::DependT<TypeTy, IdTy, ExprTy>;
using Destroy = tomp::clause::DestroyT<TypeTy, IdTy, ExprTy>;
using Detach = tomp::clause::DetachT<TypeTy, IdTy, ExprTy>;
using Device = tomp::clause::DeviceT<TypeTy, IdTy, ExprTy>;
using DeviceType = tomp::clause::DeviceTypeT<TypeTy, IdTy, ExprTy>;
using DistSchedule = tomp::clause::DistScheduleT<TypeTy, IdTy, ExprTy>;
using Doacross = tomp::clause::DoacrossT<TypeTy, IdTy, ExprTy>;
using DynamicAllocators =
tomp::clause::DynamicAllocatorsT<TypeTy, IdTy, ExprTy>;
using Enter = tomp::clause::EnterT<TypeTy, IdTy, ExprTy>;
using Exclusive = tomp::clause::ExclusiveT<TypeTy, IdTy, ExprTy>;
using Fail = tomp::clause::FailT<TypeTy, IdTy, ExprTy>;
using Filter = tomp::clause::FilterT<TypeTy, IdTy, ExprTy>;
using Final = tomp::clause::FinalT<TypeTy, IdTy, ExprTy>;
using Firstprivate = tomp::clause::FirstprivateT<TypeTy, IdTy, ExprTy>;
using From = tomp::clause::FromT<TypeTy, IdTy, ExprTy>;
using Full = tomp::clause::FullT<TypeTy, IdTy, ExprTy>;
using Grainsize = tomp::clause::GrainsizeT<TypeTy, IdTy, ExprTy>;
using HasDeviceAddr = tomp::clause::HasDeviceAddrT<TypeTy, IdTy, ExprTy>;
using Hint = tomp::clause::HintT<TypeTy, IdTy, ExprTy>;
using If = tomp::clause::IfT<TypeTy, IdTy, ExprTy>;
using Inbranch = tomp::clause::InbranchT<TypeTy, IdTy, ExprTy>;
using Inclusive = tomp::clause::InclusiveT<TypeTy, IdTy, ExprTy>;
using Indirect = tomp::clause::IndirectT<TypeTy, IdTy, ExprTy>;
using Init = tomp::clause::InitT<TypeTy, IdTy, ExprTy>;
using InReduction = tomp::clause::InReductionT<TypeTy, IdTy, ExprTy>;
using IsDevicePtr = tomp::clause::IsDevicePtrT<TypeTy, IdTy, ExprTy>;
using Lastprivate = tomp::clause::LastprivateT<TypeTy, IdTy, ExprTy>;
using Linear = tomp::clause::LinearT<TypeTy, IdTy, ExprTy>;
using Link = tomp::clause::LinkT<TypeTy, IdTy, ExprTy>;
using Map = tomp::clause::MapT<TypeTy, IdTy, ExprTy>;
using Match = tomp::clause::MatchT<TypeTy, IdTy, ExprTy>;
using Mergeable = tomp::clause::MergeableT<TypeTy, IdTy, ExprTy>;
using Message = tomp::clause::MessageT<TypeTy, IdTy, ExprTy>;
using Nocontext = tomp::clause::NocontextT<TypeTy, IdTy, ExprTy>;
using Nogroup = tomp::clause::NogroupT<TypeTy, IdTy, ExprTy>;
using Nontemporal = tomp::clause::NontemporalT<TypeTy, IdTy, ExprTy>;
using Notinbranch = tomp::clause::NotinbranchT<TypeTy, IdTy, ExprTy>;
using Novariants = tomp::clause::NovariantsT<TypeTy, IdTy, ExprTy>;
using Nowait = tomp::clause::NowaitT<TypeTy, IdTy, ExprTy>;
using NumTasks = tomp::clause::NumTasksT<TypeTy, IdTy, ExprTy>;
using NumTeams = tomp::clause::NumTeamsT<TypeTy, IdTy, ExprTy>;
using NumThreads = tomp::clause::NumThreadsT<TypeTy, IdTy, ExprTy>;
using OmpxAttribute = tomp::clause::OmpxAttributeT<TypeTy, IdTy, ExprTy>;
using OmpxBare = tomp::clause::OmpxBareT<TypeTy, IdTy, ExprTy>;
using OmpxDynCgroupMem = tomp::clause::OmpxDynCgroupMemT<TypeTy, IdTy, ExprTy>;
using Ordered = tomp::clause::OrderedT<TypeTy, IdTy, ExprTy>;
using Order = tomp::clause::OrderT<TypeTy, IdTy, ExprTy>;
using Partial = tomp::clause::PartialT<TypeTy, IdTy, ExprTy>;
using Priority = tomp::clause::PriorityT<TypeTy, IdTy, ExprTy>;
using Private = tomp::clause::PrivateT<TypeTy, IdTy, ExprTy>;
using ProcBind = tomp::clause::ProcBindT<TypeTy, IdTy, ExprTy>;
using Read = tomp::clause::ReadT<TypeTy, IdTy, ExprTy>;
using Reduction = tomp::clause::ReductionT<TypeTy, IdTy, ExprTy>;
using Relaxed = tomp::clause::RelaxedT<TypeTy, IdTy, ExprTy>;
using Release = tomp::clause::ReleaseT<TypeTy, IdTy, ExprTy>;
using ReverseOffload = tomp::clause::ReverseOffloadT<TypeTy, IdTy, ExprTy>;
using Safelen = tomp::clause::SafelenT<TypeTy, IdTy, ExprTy>;
using Schedule = tomp::clause::ScheduleT<TypeTy, IdTy, ExprTy>;
using SeqCst = tomp::clause::SeqCstT<TypeTy, IdTy, ExprTy>;
using Severity = tomp::clause::SeverityT<TypeTy, IdTy, ExprTy>;
using Shared = tomp::clause::SharedT<TypeTy, IdTy, ExprTy>;
using Simdlen = tomp::clause::SimdlenT<TypeTy, IdTy, ExprTy>;
using Simd = tomp::clause::SimdT<TypeTy, IdTy, ExprTy>;
using Sizes = tomp::clause::SizesT<TypeTy, IdTy, ExprTy>;
using TaskReduction = tomp::clause::TaskReductionT<TypeTy, IdTy, ExprTy>;
using ThreadLimit = tomp::clause::ThreadLimitT<TypeTy, IdTy, ExprTy>;
using Threads = tomp::clause::ThreadsT<TypeTy, IdTy, ExprTy>;
using To = tomp::clause::ToT<TypeTy, IdTy, ExprTy>;
using UnifiedAddress = tomp::clause::UnifiedAddressT<TypeTy, IdTy, ExprTy>;
using UnifiedSharedMemory =
tomp::clause::UnifiedSharedMemoryT<TypeTy, IdTy, ExprTy>;
using Uniform = tomp::clause::UniformT<TypeTy, IdTy, ExprTy>;
using Unknown = tomp::clause::UnknownT<TypeTy, IdTy, ExprTy>;
using Untied = tomp::clause::UntiedT<TypeTy, IdTy, ExprTy>;
using Update = tomp::clause::UpdateT<TypeTy, IdTy, ExprTy>;
using UseDeviceAddr = tomp::clause::UseDeviceAddrT<TypeTy, IdTy, ExprTy>;
using UseDevicePtr = tomp::clause::UseDevicePtrT<TypeTy, IdTy, ExprTy>;
using UsesAllocators = tomp::clause::UsesAllocatorsT<TypeTy, IdTy, ExprTy>;
using Use = tomp::clause::UseT<TypeTy, IdTy, ExprTy>;
using Weak = tomp::clause::WeakT<TypeTy, IdTy, ExprTy>;
using When = tomp::clause::WhenT<TypeTy, IdTy, ExprTy>;
using Write = tomp::clause::WriteT<TypeTy, IdTy, ExprTy>;
} // namespace clause
struct Helper {
std::optional<Object> getBaseObject(const Object &object) {
return std::nullopt;
}
std::optional<Object> getLoopIterVar() { return std::nullopt; }
};
using Clause = tomp::ClauseT<TypeTy, IdTy, ExprTy>;
using ConstructDecomposition = tomp::ConstructDecompositionT<Clause, Helper>;
using DirectiveWithClauses = tomp::DirectiveWithClauses<Clause>;
} // namespace omp
struct StringifyClause {
static std::string join(const omp::List<std::string> &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 <typename U>
static std::enable_if_t<std::is_enum_v<llvm::remove_cvref_t<U>>, std::string>
to_str(U &&Item) {
return std::to_string(llvm::to_underlying(Item));
}
template <typename U> static std::string to_str(const omp::List<U> &Items) {
omp::List<std::string> Names;
llvm::transform(Items, std::back_inserter(Names),
[](auto &&S) { return to_str(S); });
return "(" + join(Names) + ")";
}
template <typename U>
static std::string to_str(const std::optional<U> &Item) {
if (Item)
return to_str(*Item);
return "";
}
template <typename... Us, size_t... Is>
static std::string to_str(const std::tuple<Us...> &Tuple,
std::index_sequence<Is...>) {
omp::List<std::string> Strings;
(Strings.push_back(to_str(std::get<Is>(Tuple))), ...);
return "(" + join(Strings) + ")";
}
template <typename U>
static std::enable_if_t<llvm::remove_cvref_t<U>::EmptyTrait::value,
std::string>
to_str(U &&Item) {
return "";
}
template <typename U>
static std::enable_if_t<llvm::remove_cvref_t<U>::IncompleteTrait::value,
std::string>
to_str(U &&Item) {
return "";
}
template <typename U>
static std::enable_if_t<llvm::remove_cvref_t<U>::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 <typename U>
static std::enable_if_t<llvm::remove_cvref_t<U>::TupleTrait::value,
std::string>
to_str(U &&Item) {
constexpr size_t TupleSize =
std::tuple_size_v<llvm::remove_cvref_t<decltype(Item.t)>>;
return to_str(Item.t, std::make_index_sequence<TupleSize>{});
}
template <typename U>
static std::enable_if_t<llvm::remove_cvref_t<U>::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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> Clauses{
{OMPC_allocate,
omp::clause::Allocate{{std::nullopt, 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<omp::Clause> Clauses{
{OMPC_allocate,
omp::clause::Allocate{{std::nullopt, 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<omp::Clause> Clauses{
{OMPC_allocate,
omp::clause::Allocate{{std::nullopt, std::nullopt, std::nullopt, {x}}}},
{OMPC_linear,
omp::clause::Linear{{std::nullopt, 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<omp::Clause> Clauses{
{OMPC_allocate,
omp::clause::Allocate{{std::nullopt, 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<omp::Clause> Clauses{
{OMPC_allocate,
omp::clause::Allocate{{std::nullopt, 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<omp::Clause> Clauses{
{OMPC_allocate,
omp::clause::Allocate{{std::nullopt, 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> 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<omp::Clause> Clauses{
{OMPC_linear,
omp::clause::Linear{{std::nullopt, 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<omp::Clause> 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)
}
} // namespace