Skip to content

Commit f2923e3

Browse files
authored
[MLIR][OpenMP] Introduce the LoopWrapperInterface (#87232)
This patch defines a common interface to be shared by all OpenMP loop wrapper operations. The main restrictions these operations must meet in order to be considered a wrapper are: - They contain a single region. - Their region contains a single block. - Their block only contains another loop wrapper or `omp.loop_nest` and a terminator. The new interface is attached to the `omp.parallel`, `omp.wsloop`, `omp.simdloop`, `omp.distribute` and `omp.taskloop` operations. It is not currently enforced that these operations meet the wrapper restrictions, which would break existing OpenMP loop-generating code. Rather, this will be introduced progressively in subsequent patches.
1 parent 8ddaf75 commit f2923e3

File tree

5 files changed

+115
-5
lines changed

5 files changed

+115
-5
lines changed

mlir/include/mlir/Dialect/OpenMP/OpenMPInterfaces.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
#include "mlir/Interfaces/ControlFlowInterfaces.h"
2222
#include "mlir/Interfaces/SideEffectInterfaces.h"
2323

24+
#define GET_OP_FWD_DEFINES
25+
#include "mlir/Dialect/OpenMP/OpenMPOps.h.inc"
26+
2427
#include "mlir/Dialect/OpenMP/OpenMPOpsInterfaces.h.inc"
2528

2629
namespace mlir::omp {

mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ def PrivateClauseOp : OpenMP_Op<"private", [IsolatedFromAbove]> {
236236

237237
def ParallelOp : OpenMP_Op<"parallel", [
238238
AutomaticAllocationScope, AttrSizedOperandSegments,
239+
DeclareOpInterfaceMethods<LoopWrapperInterface>,
239240
DeclareOpInterfaceMethods<OutlineableOpenMPOpInterface>,
240241
RecursiveMemoryEffects, ReductionClauseInterface]> {
241242
let summary = "parallel construct";
@@ -530,8 +531,6 @@ def SingleOp : OpenMP_Op<"single", [AttrSizedOperandSegments]> {
530531

531532
def LoopNestOp : OpenMP_Op<"loop_nest", [SameVariadicOperandSize,
532533
AllTypesMatch<["lowerBound", "upperBound", "step"]>,
533-
ParentOneOf<["DistributeOp", "SimdLoopOp", "TaskloopOp",
534-
"WsloopOp"]>,
535534
RecursiveMemoryEffects]> {
536535
let summary = "rectangular loop nest";
537536
let description = [{
@@ -586,6 +585,10 @@ def LoopNestOp : OpenMP_Op<"loop_nest", [SameVariadicOperandSize,
586585

587586
/// Returns the induction variables of the loop nest.
588587
ArrayRef<BlockArgument> getIVs() { return getRegion().getArguments(); }
588+
589+
/// Fills a list of wrapper operations around this loop nest. Wrappers
590+
/// in the resulting vector will be sorted from innermost to outermost.
591+
void gatherWrappers(SmallVectorImpl<LoopWrapperInterface> &wrappers);
589592
}];
590593

591594
let hasCustomAssemblyFormat = 1;
@@ -598,6 +601,7 @@ def LoopNestOp : OpenMP_Op<"loop_nest", [SameVariadicOperandSize,
598601

599602
def WsloopOp : OpenMP_Op<"wsloop", [AttrSizedOperandSegments,
600603
AllTypesMatch<["lowerBound", "upperBound", "step"]>,
604+
DeclareOpInterfaceMethods<LoopWrapperInterface>,
601605
RecursiveMemoryEffects, ReductionClauseInterface]> {
602606
let summary = "worksharing-loop construct";
603607
let description = [{
@@ -719,7 +723,9 @@ def WsloopOp : OpenMP_Op<"wsloop", [AttrSizedOperandSegments,
719723
//===----------------------------------------------------------------------===//
720724

721725
def SimdLoopOp : OpenMP_Op<"simdloop", [AttrSizedOperandSegments,
722-
AllTypesMatch<["lowerBound", "upperBound", "step"]>]> {
726+
AllTypesMatch<["lowerBound", "upperBound", "step"]>,
727+
DeclareOpInterfaceMethods<LoopWrapperInterface>,
728+
RecursiveMemoryEffects]> {
723729
let summary = "simd loop construct";
724730
let description = [{
725731
The simd construct can be applied to a loop to indicate that the loop can be
@@ -833,7 +839,8 @@ def YieldOp : OpenMP_Op<"yield",
833839
// Distribute construct [2.9.4.1]
834840
//===----------------------------------------------------------------------===//
835841
def DistributeOp : OpenMP_Op<"distribute", [AttrSizedOperandSegments,
836-
MemoryEffects<[MemWrite]>]> {
842+
DeclareOpInterfaceMethods<LoopWrapperInterface>,
843+
RecursiveMemoryEffects]> {
837844
let summary = "distribute construct";
838845
let description = [{
839846
The distribute construct specifies that the iterations of one or more loops
@@ -1011,6 +1018,7 @@ def TaskOp : OpenMP_Op<"task", [AttrSizedOperandSegments,
10111018
def TaskloopOp : OpenMP_Op<"taskloop", [AttrSizedOperandSegments,
10121019
AutomaticAllocationScope, RecursiveMemoryEffects,
10131020
AllTypesMatch<["lowerBound", "upperBound", "step"]>,
1021+
DeclareOpInterfaceMethods<LoopWrapperInterface>,
10141022
ReductionClauseInterface]> {
10151023
let summary = "taskloop construct";
10161024
let description = [{

mlir/include/mlir/Dialect/OpenMP/OpenMPOpsInterfaces.td

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,73 @@ def ReductionClauseInterface : OpInterface<"ReductionClauseInterface"> {
6969
];
7070
}
7171

72+
def LoopWrapperInterface : OpInterface<"LoopWrapperInterface"> {
73+
let description = [{
74+
OpenMP operations that can wrap a single loop nest. When taking a wrapper
75+
role, these operations must only contain a single region with a single block
76+
in which there's a single operation and a terminator. That nested operation
77+
must be another loop wrapper or an `omp.loop_nest`.
78+
}];
79+
80+
let cppNamespace = "::mlir::omp";
81+
82+
let methods = [
83+
InterfaceMethod<
84+
/*description=*/[{
85+
Tell whether the operation could be taking the role of a loop wrapper.
86+
That is, it has a single region with a single block in which there are
87+
two operations: another wrapper or `omp.loop_nest` operation and a
88+
terminator.
89+
}],
90+
/*retTy=*/"bool",
91+
/*methodName=*/"isWrapper",
92+
(ins ), [{}], [{
93+
if ($_op->getNumRegions() != 1)
94+
return false;
95+
96+
Region &r = $_op->getRegion(0);
97+
if (!r.hasOneBlock())
98+
return false;
99+
100+
if (::llvm::range_size(r.getOps()) != 2)
101+
return false;
102+
103+
Operation &firstOp = *r.op_begin();
104+
Operation &secondOp = *(std::next(r.op_begin()));
105+
return ::llvm::isa<LoopNestOp, LoopWrapperInterface>(firstOp) &&
106+
secondOp.hasTrait<OpTrait::IsTerminator>();
107+
}]
108+
>,
109+
InterfaceMethod<
110+
/*description=*/[{
111+
If there is another loop wrapper immediately nested inside, return that
112+
operation. Assumes this operation is taking a loop wrapper role.
113+
}],
114+
/*retTy=*/"::mlir::omp::LoopWrapperInterface",
115+
/*methodName=*/"getNestedWrapper",
116+
(ins), [{}], [{
117+
assert($_op.isWrapper() && "Unexpected non-wrapper op");
118+
Operation *nested = &*$_op->getRegion(0).op_begin();
119+
return ::llvm::dyn_cast<LoopWrapperInterface>(nested);
120+
}]
121+
>,
122+
InterfaceMethod<
123+
/*description=*/[{
124+
Return the loop nest nested directly or indirectly inside of this loop
125+
wrapper. Assumes this operation is taking a loop wrapper role.
126+
}],
127+
/*retTy=*/"::mlir::Operation *",
128+
/*methodName=*/"getWrappedLoop",
129+
(ins), [{}], [{
130+
assert($_op.isWrapper() && "Unexpected non-wrapper op");
131+
if (LoopWrapperInterface nested = $_op.getNestedWrapper())
132+
return nested.getWrappedLoop();
133+
return &*$_op->getRegion(0).op_begin();
134+
}]
135+
>
136+
];
137+
}
138+
72139
def DeclareTargetInterface : OpInterface<"DeclareTargetInterface"> {
73140
let description = [{
74141
OpenMP operations that support declare target have this interface.

mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1936,9 +1936,27 @@ LogicalResult LoopNestOp::verify() {
19361936
<< "range argument type does not match corresponding IV type";
19371937
}
19381938

1939+
auto wrapper =
1940+
llvm::dyn_cast_if_present<LoopWrapperInterface>((*this)->getParentOp());
1941+
1942+
if (!wrapper || !wrapper.isWrapper())
1943+
return emitOpError() << "expects parent op to be a valid loop wrapper";
1944+
19391945
return success();
19401946
}
19411947

1948+
void LoopNestOp::gatherWrappers(
1949+
SmallVectorImpl<LoopWrapperInterface> &wrappers) {
1950+
Operation *parent = (*this)->getParentOp();
1951+
while (auto wrapper =
1952+
llvm::dyn_cast_if_present<LoopWrapperInterface>(parent)) {
1953+
if (!wrapper.isWrapper())
1954+
break;
1955+
wrappers.push_back(wrapper);
1956+
parent = parent->getParentOp();
1957+
}
1958+
}
1959+
19421960
//===----------------------------------------------------------------------===//
19431961
// WsloopOp
19441962
//===----------------------------------------------------------------------===//

mlir/test/Dialect/OpenMP/invalid.mlir

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,28 @@ func.func @proc_bind_once() {
8888
// -----
8989

9090
func.func @invalid_parent(%lb : index, %ub : index, %step : index) {
91-
// expected-error@+1 {{op expects parent op to be one of 'omp.distribute, omp.simdloop, omp.taskloop, omp.wsloop'}}
91+
// expected-error@+1 {{op expects parent op to be a valid loop wrapper}}
9292
omp.loop_nest (%iv) : index = (%lb) to (%ub) step (%step) {
9393
omp.yield
9494
}
9595
}
9696

9797
// -----
9898

99+
func.func @invalid_wrapper(%lb : index, %ub : index, %step : index) {
100+
// TODO Remove induction variables from omp.wsloop.
101+
omp.wsloop for (%iv) : index = (%lb) to (%ub) step (%step) {
102+
%0 = arith.constant 0 : i32
103+
// expected-error@+1 {{op expects parent op to be a valid loop wrapper}}
104+
omp.loop_nest (%iv2) : index = (%lb) to (%ub) step (%step) {
105+
omp.yield
106+
}
107+
omp.yield
108+
}
109+
}
110+
111+
// -----
112+
99113
func.func @type_mismatch(%lb : index, %ub : index, %step : index) {
100114
// TODO Remove induction variables from omp.wsloop.
101115
omp.wsloop for (%iv) : index = (%lb) to (%ub) step (%step) {

0 commit comments

Comments
 (0)