Skip to content

[Frontend][OpenMP] Implement getLeafOrCompositeConstructs #89104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c9ee93f
[MLIR][OpenMP] Group clause operands into structures
skatrak Mar 26, 2024
7af7e9d
[Flang][OpenMP][Lower] Use clause operand structures
skatrak Mar 26, 2024
e291fad
[Flang][OpenMP][Lower] Split MLIR codegen for clauses and constructs
skatrak Mar 28, 2024
d56c859
Merge branch 'main' into users/skatrak/spr/clause-operands-01-mlir
skatrak Mar 28, 2024
3d9ec39
Merge branch 'users/skatrak/spr/clause-operands-01-mlir' into users/s…
skatrak Mar 28, 2024
0416ec4
Merge branch 'users/skatrak/spr/clause-operands-02-flang' into users/…
skatrak Mar 28, 2024
ec0ed50
[Flang][OpenMP][Lower] Refactor lowering of compound constructs
skatrak Mar 29, 2024
f725fac
[flang][OpenMP] Move clause/object conversion to happen early, in genOMP
kparzysz Mar 29, 2024
291dc48
[Frontend][OpenMP] Refactor getLeafConstructs, add getCompoundConstruct
kparzysz Apr 1, 2024
a889f30
[Frontend][OpenMP] Add functions for checking construct type
kparzysz Mar 11, 2024
0d92781
Address review comments
kparzysz Apr 2, 2024
46770f8
[flang][OpenMP] Move clause/object conversion to happen early, in genOMP
kparzysz Mar 29, 2024
de93771
Merge branch 'main' into users/kparzysz/spr/a05-makeclause
kparzysz Apr 17, 2024
065b54c
clang-format
kparzysz Apr 17, 2024
e799507
Merge branch 'users/kparzysz/spr/a05-makeclause' into users/kparzysz/…
kparzysz Apr 17, 2024
a803e4a
Merge branch 'users/kparzysz/spr/a06-leafsorcomposite' into users/kpa…
kparzysz Apr 17, 2024
cb7c0f8
Rename test
kparzysz Apr 17, 2024
8f935fb
Finish the renaming
kparzysz Apr 17, 2024
4593582
[LLVM][OpenMP] Implement getLeafOrCompositeConstructs
kparzysz Apr 11, 2024
a626be8
Address review comments
kparzysz Apr 22, 2024
a37ae86
Revert "Address review comments"
kparzysz Apr 22, 2024
62b72ef
Merge branch 'main' into users/kparzysz/spr/a08-leafsandcomposite
kparzysz Apr 23, 2024
02bbf90
Clarify the interface of `getFirstCompositeRange`
kparzysz Apr 23, 2024
cc539db
Add assertion to verify that composite construct consumed all remaini…
kparzysz Apr 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions llvm/include/llvm/Frontend/OpenMP/OMP.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@
#include "llvm/Frontend/OpenMP/OMP.h.inc"

#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"

namespace llvm::omp {
ArrayRef<Directive> getLeafConstructs(Directive D);
ArrayRef<Directive> getLeafConstructsOrSelf(Directive D);

ArrayRef<Directive>
getLeafOrCompositeConstructs(Directive D, SmallVectorImpl<Directive> &Output);

Directive getCompoundConstruct(ArrayRef<Directive> Parts);

bool isLeafConstruct(Directive D);
Expand Down
103 changes: 90 additions & 13 deletions llvm/lib/Frontend/OpenMP/OMP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,54 @@ using namespace llvm::omp;
#define GEN_DIRECTIVES_IMPL
#include "llvm/Frontend/OpenMP/OMP.inc"

static iterator_range<ArrayRef<Directive>::iterator>
getFirstCompositeRange(iterator_range<ArrayRef<Directive>::iterator> Leafs) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that there can only be at most a single composite construct when decomposing any given compound construct. So there wouldn't be a "first" composite range, but rather one composite range or none. In fact, if there is a composite range, it must also be at the end of the construct.

So, something like this: <composite-construct><other-leaf> can't happen. Decomposing a single compound construct into a list of leaves while keeping composite constructs as a unit should be as simple as forwarding all block-associated leaves until finding a loop-associated leaf and then aggregating (via getCompoundConstruct()) everything from there until the end of the leaf list to append one composite or single loop construct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intended to be a general utility to find the first sequence of leaf constructs that together constitute a composite construct. The input leafs aren't assumed to represent a legal construct.
It's also used in isCompositeConstruct as well, where I break up the given directive into leafs and check if the "first composite range" is equal to the sequence of leafs.

Copy link
Member

@skatrak skatrak Apr 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason for these functions to allow unsupported lists of directives as input? I think it's worth having a simpler implementation over allowing all sorts of invalid lists of leaf constructs as input, unless there's a good reason why we'd need that.

Not saying that the compiler should crash in that case, but they can just return empty ranges for unsupported lists of leaf constructs rather than trying to find subsets that match what we're looking for.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created the concept that this function implements as a common element in implementing other functions. This is a utility function that has a clearly defined functionality, that only depends on what is considered a "composite" construct. It is not an API function and it doesn't have to concern itself with validating the inputs, that's left to the API functions.
There are other ways to implement the breakdown of compound constructs, yours is likely another valid approach, but I've picked this one because of its flexibility.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean. It still feels a bit overkill to me, but I don't have a big issue with it since it's a private function to this compilation unit, so it's not a blocker.

// OpenMP Spec 5.2: [17.3, 8-9]
// If directive-name-A and directive-name-B both correspond to loop-
// associated constructs then directive-name is a composite construct
// otherwise directive-name is a combined construct.
//
// In the list of leaf constructs, find the first loop-associated construct,
// this is the beginning of the returned range. Then, starting from the
// immediately following leaf construct, find the first sequence of adjacent
// loop-associated constructs. The last of those is the last one of the
// range, that is, the end of the range is one past that element.
// If such a sequence of adjacent loop-associated directives does not exist,
// return an empty range.
//
// The end of the returned range (including empty range) is intended to be
// a point from which the search for the next range could resume.
//
// Consequently, this function can't return a range with a single leaf
// construct in it.

auto firstLoopAssociated =
[](iterator_range<ArrayRef<Directive>::iterator> List) {
for (auto It = List.begin(), End = List.end(); It != End; ++It) {
if (getDirectiveAssociation(*It) == Association::Loop)
return It;
}
return List.end();
};

auto Empty = llvm::make_range(Leafs.end(), Leafs.end());

auto Begin = firstLoopAssociated(Leafs);
if (Begin == Leafs.end())
return Empty;

auto End =
firstLoopAssociated(llvm::make_range(std::next(Begin), Leafs.end()));
if (End == Leafs.end())
return Empty;

for (; End != Leafs.end(); ++End) {
if (getDirectiveAssociation(*End) != Association::Loop)
break;
}
return llvm::make_range(Begin, End);
}

namespace llvm::omp {
ArrayRef<Directive> getLeafConstructs(Directive D) {
auto Idx = static_cast<std::size_t>(D);
Expand All @@ -34,6 +82,44 @@ ArrayRef<Directive> getLeafConstructs(Directive D) {
return ArrayRef(&Row[2], static_cast<int>(Row[1]));
}

ArrayRef<Directive> getLeafConstructsOrSelf(Directive D) {
if (auto Leafs = getLeafConstructs(D); !Leafs.empty())
return Leafs;
auto Idx = static_cast<size_t>(D);
assert(Idx < Directive_enumSize && "Invalid directive");
const auto *Row = LeafConstructTable[LeafConstructTableOrdering[Idx]];
// The first entry in the row is the directive itself.
return ArrayRef(&Row[0], &Row[0] + 1);
}

ArrayRef<Directive>
getLeafOrCompositeConstructs(Directive D, SmallVectorImpl<Directive> &Output) {
using ArrayTy = ArrayRef<Directive>;
using IteratorTy = ArrayTy::iterator;
ArrayRef<Directive> Leafs = getLeafConstructsOrSelf(D);

IteratorTy Iter = Leafs.begin();
do {
auto Range = getFirstCompositeRange(llvm::make_range(Iter, Leafs.end()));
// All directives before the range are leaf constructs.
for (; Iter != Range.begin(); ++Iter)
Output.push_back(*Iter);
if (!Range.empty()) {
Directive Comp =
getCompoundConstruct(ArrayTy(Range.begin(), Range.end()));
assert(Comp != OMPD_unknown);
Output.push_back(Comp);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should check here that Range.end() == Leafs.end() and return an empty list in that case (or some other way to signal an invalid input), because otherwise we'd be allowing something like [distribute, simd, parallel] to be passed in and then return [distribute-simd, parallel]. Or is this also intended to be very lenient and callers are responsible for only dealing with valid composite constructs? Since it's checked that there's actually a valid composite construct, I think this other check should be done as well.

Do we need to return the Output argument?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This starts with an actual directive D. It is then decomposed into either leaf or composite constructs, so presumably there shouldn't be a situation where there is a composite construct followed by something else. I think we should assert on that (instead of returning an empty list), because it would indicate some kind of an internal inconsistency and this is an API function.

We don't need to return Output---I meant it to be usable in cases like

for (auto &c : getLeafOrCompositeConstructs(D, Output)) {
  ...
}

Iter = Range.end();
// As of now, a composite construct must contain all constituent leaf
// constructs from some point until the end of all constituent leaf
// constructs.
assert(Iter == Leafs.end() && "Malformed directive");
}
} while (Iter != Leafs.end());

return Output;
}

Directive getCompoundConstruct(ArrayRef<Directive> Parts) {
if (Parts.empty())
return OMPD_unknown;
Expand Down Expand Up @@ -88,20 +174,11 @@ Directive getCompoundConstruct(ArrayRef<Directive> Parts) {
bool isLeafConstruct(Directive D) { return getLeafConstructs(D).empty(); }

bool isCompositeConstruct(Directive D) {
// OpenMP Spec 5.2: [17.3, 8-9]
// If directive-name-A and directive-name-B both correspond to loop-
// associated constructs then directive-name is a composite construct
llvm::ArrayRef<Directive> Leafs{getLeafConstructs(D)};
if (Leafs.empty())
return false;
if (getDirectiveAssociation(Leafs.front()) != Association::Loop)
ArrayRef<Directive> Leafs = getLeafConstructsOrSelf(D);
if (Leafs.size() <= 1)
return false;

size_t numLoopConstructs =
llvm::count_if(Leafs.drop_front(), [](Directive L) {
return getDirectiveAssociation(L) == Association::Loop;
});
return numLoopConstructs != 0;
auto Range = getFirstCompositeRange(Leafs);
return Range.begin() == Leafs.begin() && Range.end() == Leafs.end();
}

bool isCombinedConstruct(Directive D) {
Expand Down
32 changes: 32 additions & 0 deletions llvm/unittests/Frontend/OpenMPCompositionTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//

#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Frontend/OpenMP/OMP.h"
#include "gtest/gtest.h"

Expand Down Expand Up @@ -40,6 +41,37 @@ TEST(Composition, GetCompoundConstruct) {
ASSERT_EQ(C7, OMPD_do_simd); // Make sure it's not OMPD_end_do_simd
}

TEST(Composition, GetLeafOrCompositeConstructs) {
SmallVector<Directive> Out1;
auto Ret1 = getLeafOrCompositeConstructs(
OMPD_target_teams_distribute_parallel_for, Out1);
ASSERT_EQ(Ret1, ArrayRef<Directive>(Out1));
ASSERT_EQ((ArrayRef<Directive>(Out1)),
(ArrayRef<Directive>{OMPD_target, OMPD_teams,
OMPD_distribute_parallel_for}));

SmallVector<Directive> Out2;
auto Ret2 =
getLeafOrCompositeConstructs(OMPD_parallel_masked_taskloop_simd, Out2);
ASSERT_EQ(Ret2, ArrayRef<Directive>(Out2));
ASSERT_EQ(
(ArrayRef<Directive>(Out2)),
(ArrayRef<Directive>{OMPD_parallel, OMPD_masked, OMPD_taskloop_simd}));

SmallVector<Directive> Out3;
auto Ret3 =
getLeafOrCompositeConstructs(OMPD_distribute_parallel_do_simd, Out3);
ASSERT_EQ(Ret3, ArrayRef<Directive>(Out3));
ASSERT_EQ((ArrayRef<Directive>(Out3)),
(ArrayRef<Directive>{OMPD_distribute_parallel_do_simd}));

SmallVector<Directive> Out4;
auto Ret4 = getLeafOrCompositeConstructs(OMPD_target_parallel_loop, Out4);
ASSERT_EQ(Ret4, ArrayRef<Directive>(Out4));
ASSERT_EQ((ArrayRef<Directive>(Out4)),
(ArrayRef<Directive>{OMPD_target, OMPD_parallel, OMPD_loop}));
}

TEST(Composition, IsLeafConstruct) {
ASSERT_TRUE(isLeafConstruct(OMPD_loop));
ASSERT_TRUE(isLeafConstruct(OMPD_teams));
Expand Down