-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[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
Conversation
This patch introduces a set of composable structures grouping the MLIR operands associated to each OpenMP clause. This makes it easier to keep the MLIR representation for the same clause consistent throughout all operations that accept it. The relevant clause operand structures are grouped into per-operation structures using a mixin pattern and used to define new operation constructors. These constructors can be used to avoid having to get the order of a possibly large list of operands right. Missing clauses are documented as TODOs, as well as operands which are part of the relevant operation's operand structure but cannot be attached to the associated operation yet, due to missing op arguments to its MLIR definition. A follow-up patch will update Flang lowering to make use of these structures, simplifying the passing of information from clause processing to operation- generating functions and also simplifying the creation of operations through the use of the new operation constructors.
This patch updates Flang lowering to use the new set of OpenMP clause operand structures and their groupings into directive-specific sets of clause operands. It simplifies the passing of information from the clause processor and the creation of operations. The `DataSharingProcessor` is slightly modified to not hold delayed privatization state. Instead, optional arguments are added to `processStep1` which are only passed when delayed privatization is used. This enables using the clause operand structure for `private` and removes the need for the ad-hoc `DelayedPrivatizationInfo` structure. The processing of the `schedule` clause is updated to process the `chunk` modifier rather than requiring two separate calls to the `ClauseProcessor`. Lowering of a block-associated `ordered` construct is updated to emit a TODO error if the `simd` clause is specified, since it is not currently supported by the `ClauseProcessor` or later compilation stages. Removed processing of `schedule` from `omp.simdloop`, as it doesn't apply to `simd` constructs.
This patch performs several cleanups with the main purpose of normalizing the code patterns used to trigger codegen for MLIR OpenMP operations and making the processing of clauses and constructs independent. The following changes are made: - Clean up unused `directive` argument to `ClauseProcessor::processMap()`. - Move general helper functions in OpenMP.cpp to the appropriate section of the file. - Create `gen<OpName>Clauses()` functions containing the clause processing code specific for the associated OpenMP construct. - Update `gen<OpName>Op()` functions to call the corresponding `gen<OpName>Clauses()` function. - Sort calls to `ClauseProcessor::process<ClauseName>()` alphabetically, to avoid inadvertently relying on some arbitrary order. Update some tests that broke due to the order change. - Normalize `genOMP()` functions so they all delegate the generation of MLIR to `gen<OpName>Op()` functions following the same pattern. - Only process `nowait` clause on `TARGET` constructs if not compiling for the target device. A later patch can move the calls to `gen<OpName>Clauses()` out of `gen<OpName>Op()` functions and passing completed clause structures instead, in preparation to supporting composite constructs. That will make it possible to reuse clause processing for a given leaf construct when appearing alone or in a combined or composite construct, while controlling where the associated code is produced.
…katrak/spr/clause-operands-02-flang
…skatrak/spr/clause-operands-03-genopclauses
This patch simplifies the lowering from PFT to MLIR of OpenMP compound constructs (i.e. combined and composite). The new approach consists of iteratively processing the outermost leaf construct of the given combined construct until it cannot be split further. Both leaf constructs and composite ones have `gen...()` functions that are called when appropriate. This approach enables treating a leaf construct the same way regardless of if it appeared as part of a combined construct, and it also enables the lowering of composite constructs as a single unit. Previous corner cases are now handled in a more straightforward way and comments pointing to the relevant spec section are added. Directive sets are also completed with missing LOOP related constructs.
This removes the last use of genOmpObectList2, which has now been removed.
Emit a special leaf constuct table in DirectiveEmitter.cpp, which will allow both decomposition of a construct into leafs, and composition of constituent constructs into a single compound construct (is possible).
Implement helper functions to identify leaf, composite, and combined constructs.
This removes the last use of genOmpObjectList2, which has now been removed.
…spr/a06-leafsorcomposite
…rzysz/spr/a07-construct-type
This function will break up a construct into constituent leaf and composite constructs, e.g. if OMPD_c_d_e and OMPD_d_e are composite constructs, then OMPD_a_b_c_d_e will be broken up into the list {OMPD_a, OMPD_b, OMPD_c_d_e}.
@llvm/pr-subscribers-flang-openmp Author: Krzysztof Parzyszek (kparzysz) ChangesThis function will break up a construct into constituent leaf and composite constructs, e.g. if OMPD_c_d_e and OMPD_d_e are composite constructs, then OMPD_a_b_c_d_e will be broken up into the list {OMPD_a, OMPD_b, OMPD_c_d_e}. Full diff: https://github.com/llvm/llvm-project/pull/89104.diff 3 Files Affected:
diff --git a/llvm/include/llvm/Frontend/OpenMP/OMP.h b/llvm/include/llvm/Frontend/OpenMP/OMP.h
index ec8ae68f1c2ca0..6f7a39acac1d31 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMP.h
+++ b/llvm/include/llvm/Frontend/OpenMP/OMP.h
@@ -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);
diff --git a/llvm/lib/Frontend/OpenMP/OMP.cpp b/llvm/lib/Frontend/OpenMP/OMP.cpp
index 4b9b7037ee4ad5..2ebadf5216a084 100644
--- a/llvm/lib/Frontend/OpenMP/OMP.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMP.cpp
@@ -25,6 +25,43 @@ 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) {
+ // 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 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.
+
+ 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 Begin = firstLoopAssociated(Leafs);
+ if (Begin == Leafs.end())
+ return llvm::make_range(Leafs.end(), Leafs.end());
+
+ auto End =
+ firstLoopAssociated(llvm::make_range(std::next(Begin), Leafs.end()));
+ if (End == Leafs.end())
+ return llvm::make_range(Begin, std::next(Begin));
+
+ 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);
@@ -34,6 +71,40 @@ ArrayRef<Directive> getLeafConstructs(Directive D) {
return ArrayRef(&Row[2], &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);
+ }
+ Iter = Range.end();
+ } while (Iter != Leafs.end());
+
+ return Output;
+}
+
Directive getCompoundConstruct(ArrayRef<Directive> Parts) {
if (Parts.empty())
return OMPD_unknown;
@@ -88,20 +159,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) {
diff --git a/llvm/unittests/Frontend/OpenMPCompositionTest.cpp b/llvm/unittests/Frontend/OpenMPCompositionTest.cpp
index 0b32e0d96dc84c..6915a0cbcaac2d 100644
--- a/llvm/unittests/Frontend/OpenMPCompositionTest.cpp
+++ b/llvm/unittests/Frontend/OpenMPCompositionTest.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/Frontend/OpenMP/OMP.h"
#include "gtest/gtest.h"
@@ -38,6 +39,37 @@ TEST(Composition, GetCompoundConstruct) {
ASSERT_EQ(C6, OMPD_parallel_for_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));
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you Krzysztof for working on this. I have a suggestion to simplify the implementation quite a bit, but let me know if you have issues with it.
@@ -25,6 +25,43 @@ 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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
This reverts commit a626be8.
Push review updates for another PR here by mistake... |
Directive Comp = | ||
getCompoundConstruct(ArrayTy(Range.begin(), Range.end())); | ||
assert(Comp != OMPD_unknown); | ||
Output.push_back(Comp); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)) {
...
}
@@ -25,6 +25,43 @@ 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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Krzysztof for addressing my concerns. This should be good to go after adding the assert to getLeafOrCompositeConstructs
, so I'm approving it already to avoid blocking it any longer. Perhaps give it until tomorrow in case anyone else has any concerns.
Windows build failure is due to |
This function will break up a construct into constituent leaf and composite constructs, e.g. if OMPD_c_d_e and OMPD_d_e are composite constructs, then OMPD_a_b_c_d_e will be broken up into the list {OMPD_a, OMPD_b, OMPD_c_d_e}.