Skip to content

Commit 21122cf

Browse files
committed
[MLIR][OpenMP] Add omp.loopnest operation
This patch introduces the `omp.loopnest` MLIR operation, which contains the information of a rectangular collapsed loop nest. It mirrors the existing representation of `omp.wsloop`, `omp.simdloop` and `omp.taskloop`, with the intent of removing loop information from these in a subsequent patch. This representation is a temporary solution that does not address loop transformations. That is the goal of the `omp.canonical_loop` discussion.
1 parent cdc03d8 commit 21122cf

File tree

4 files changed

+250
-1
lines changed

4 files changed

+250
-1
lines changed

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

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,66 @@ def SingleOp : OpenMP_Op<"single", [AttrSizedOperandSegments]> {
506506
let hasVerifier = 1;
507507
}
508508

509+
//===----------------------------------------------------------------------===//
510+
// Loop Nest
511+
//===----------------------------------------------------------------------===//
512+
513+
def LoopNestOp : OpenMP_Op<"loopnest", [SameVariadicOperandSize,
514+
AllTypesMatch<["lowerBound", "upperBound", "step"]>,
515+
ParentOneOf<["DistributeOp", "SimdLoopOp", "TaskLoopOp",
516+
"WsLoopOp"]>,
517+
RecursiveMemoryEffects]> {
518+
let summary = "canonical loop";
519+
let description = [{
520+
This operation represents a collapsed rectangular loop nest. For each
521+
rectangular loop of the nest represented by an instance of this operation,
522+
lower and upper bounds, as well as a step variable, must be defined.
523+
524+
The lower and upper bounds specify a half-open range: the range includes the
525+
lower bound but does not include the upper bound. If the `inclusive`
526+
attribute is specified then the upper bound is also included.
527+
528+
The body region can contain any number of blocks. The region is terminated
529+
by "omp.yield" instruction without operands. The induction variables,
530+
represented as entry block arguments to the canonical loop operation's
531+
single region, match the types of the `lowerBound`, `upperBound` and `step`
532+
arguments.
533+
534+
```mlir
535+
omp.loopnest (%i1, %i2) : i32 = (%c0, %c0) to (%c10, %c10) step (%c1, %c1) {
536+
%a = load %arrA[%i1, %i2] : memref<?x?xf32>
537+
%b = load %arrB[%i1, %i2] : memref<?x?xf32>
538+
%sum = arith.addf %a, %b : f32
539+
store %sum, %arrC[%i1, %i2] : memref<?x?xf32>
540+
omp.yield
541+
}
542+
```
543+
544+
This is a temporary simplified definition of a loop based on existing OpenMP
545+
loop operations intended to serve as a stopgap solution until the long-term
546+
representation of canonical loops is defined. Specifically, this approach is
547+
not intended to help with the addition of support for loop transformations.
548+
}];
549+
550+
let arguments = (ins Variadic<IntLikeType>:$lowerBound,
551+
Variadic<IntLikeType>:$upperBound,
552+
Variadic<IntLikeType>:$step,
553+
UnitAttr:$inclusive);
554+
555+
let regions = (region AnyRegion:$region);
556+
557+
let extraClassDeclaration = [{
558+
/// Returns the number of loops in the loop nest.
559+
unsigned getNumLoops() { return getLowerBound().size(); }
560+
561+
/// Returns the induction variables of the loop nest.
562+
ArrayRef<BlockArgument> getIVs() { return getRegion().getArguments(); }
563+
}];
564+
565+
let hasCustomAssemblyFormat = 1;
566+
let hasVerifier = 1;
567+
}
568+
509569
//===----------------------------------------------------------------------===//
510570
// 2.9.2 Workshare Loop Construct
511571
//===----------------------------------------------------------------------===//
@@ -714,7 +774,7 @@ def SimdLoopOp : OpenMP_Op<"simdloop", [AttrSizedOperandSegments,
714774

715775
def YieldOp : OpenMP_Op<"yield",
716776
[Pure, ReturnLike, Terminator,
717-
ParentOneOf<["WsLoopOp", "ReductionDeclareOp",
777+
ParentOneOf<["LoopNestOp", "WsLoopOp", "ReductionDeclareOp",
718778
"AtomicUpdateOp", "SimdLoopOp", "PrivateClauseOp"]>]> {
719779
let summary = "loop yield and termination operation";
720780
let description = [{

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1487,6 +1487,77 @@ LogicalResult SingleOp::verify() {
14871487
getCopyprivateFuncs());
14881488
}
14891489

1490+
//===----------------------------------------------------------------------===//
1491+
// LoopNestOp
1492+
//===----------------------------------------------------------------------===//
1493+
1494+
ParseResult LoopNestOp::parse(OpAsmParser &parser, OperationState &result) {
1495+
// Parse an opening `(` followed by induction variables followed by `)`
1496+
SmallVector<OpAsmParser::Argument> ivs;
1497+
SmallVector<OpAsmParser::UnresolvedOperand> lbs, ubs;
1498+
Type loopVarType;
1499+
if (parser.parseArgumentList(ivs, OpAsmParser::Delimiter::Paren) ||
1500+
parser.parseColonType(loopVarType) ||
1501+
// Parse loop bounds.
1502+
parser.parseEqual() ||
1503+
parser.parseOperandList(lbs, ivs.size(), OpAsmParser::Delimiter::Paren) ||
1504+
parser.parseKeyword("to") ||
1505+
parser.parseOperandList(ubs, ivs.size(), OpAsmParser::Delimiter::Paren))
1506+
return failure();
1507+
1508+
for (auto &iv : ivs)
1509+
iv.type = loopVarType;
1510+
1511+
// Parse "inclusive" flag.
1512+
if (succeeded(parser.parseOptionalKeyword("inclusive")))
1513+
result.addAttribute("inclusive",
1514+
UnitAttr::get(parser.getBuilder().getContext()));
1515+
1516+
// Parse step values.
1517+
SmallVector<OpAsmParser::UnresolvedOperand> steps;
1518+
if (parser.parseKeyword("step") ||
1519+
parser.parseOperandList(steps, ivs.size(), OpAsmParser::Delimiter::Paren))
1520+
return failure();
1521+
1522+
// Parse the body.
1523+
Region *region = result.addRegion();
1524+
if (parser.parseRegion(*region, ivs))
1525+
return failure();
1526+
1527+
// Resolve operands.
1528+
if (parser.resolveOperands(lbs, loopVarType, result.operands) ||
1529+
parser.resolveOperands(ubs, loopVarType, result.operands) ||
1530+
parser.resolveOperands(steps, loopVarType, result.operands))
1531+
return failure();
1532+
1533+
// Parse the optional attribute list.
1534+
return parser.parseOptionalAttrDict(result.attributes);
1535+
}
1536+
1537+
void LoopNestOp::print(OpAsmPrinter &p) {
1538+
Region &region = getRegion();
1539+
auto args = region.getArguments();
1540+
p << " (" << args << ") : " << args[0].getType() << " = (" << getLowerBound()
1541+
<< ") to (" << getUpperBound() << ") ";
1542+
if (getInclusive())
1543+
p << "inclusive ";
1544+
p << "step (" << getStep() << ") ";
1545+
p.printRegion(region, /*printEntryBlockArgs=*/false);
1546+
}
1547+
1548+
LogicalResult LoopNestOp::verify() {
1549+
if (getLowerBound().size() != getIVs().size())
1550+
return emitOpError() << "number of range arguments and IVs do not match";
1551+
1552+
for (auto [lb, iv] : llvm::zip_equal(getLowerBound(), getIVs())) {
1553+
if (lb.getType() != iv.getType())
1554+
return emitOpError()
1555+
<< "range argument type does not match corresponding IV type";
1556+
}
1557+
1558+
return success();
1559+
}
1560+
14901561
//===----------------------------------------------------------------------===//
14911562
// WsLoopOp
14921563
//===----------------------------------------------------------------------===//

mlir/test/Dialect/OpenMP/invalid.mlir

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,43 @@ func.func @proc_bind_once() {
8787

8888
// -----
8989

90+
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'}}
92+
omp.loopnest (%iv) : index = (%lb) to (%ub) step (%step) {
93+
omp.yield
94+
}
95+
}
96+
97+
// -----
98+
99+
func.func @type_mismatch(%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+
// expected-error@+1 {{range argument type does not match corresponding IV type}}
103+
"omp.loopnest" (%lb, %ub, %step) ({
104+
^bb0(%iv2: i32):
105+
omp.yield
106+
}) : (index, index, index) -> ()
107+
omp.yield
108+
}
109+
}
110+
111+
// -----
112+
113+
func.func @iv_number_mismatch(%lb : index, %ub : index, %step : index) {
114+
// TODO Remove induction variables from omp.wsloop.
115+
omp.wsloop for (%iv) : index = (%lb) to (%ub) step (%step) {
116+
// expected-error@+1 {{number of range arguments and IVs do not match}}
117+
"omp.loopnest" (%lb, %ub, %step) ({
118+
^bb0(%iv1 : index, %iv2 : index):
119+
omp.yield
120+
}) : (index, index, index) -> ()
121+
omp.yield
122+
}
123+
}
124+
125+
// -----
126+
90127
func.func @inclusive_not_a_clause(%lb : index, %ub : index, %step : index) {
91128
// expected-error @below {{expected 'for'}}
92129
omp.wsloop nowait inclusive

mlir/test/Dialect/OpenMP/ops.mlir

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,87 @@ func.func @omp_parallel_pretty(%data_var : memref<i32>, %if_cond : i1, %num_thre
133133
return
134134
}
135135

136+
// CHECK-LABEL: omp_loopnest
137+
func.func @omp_loopnest(%lb : index, %ub : index, %step : index) -> () {
138+
139+
// TODO Remove induction variables from omp.wsloop.
140+
omp.wsloop for (%iv) : index = (%lb) to (%ub) step (%step) {
141+
// CHECK: omp.loopnest
142+
// CHECK-SAME: (%{{.*}}) : index =
143+
// CHECK-SAME: (%{{.*}}) to (%{{.*}}) step (%{{.*}})
144+
"omp.loopnest" (%lb, %ub, %step) ({
145+
^bb0(%iv2: index):
146+
omp.yield
147+
}) : (index, index, index) -> ()
148+
omp.yield
149+
}
150+
151+
// TODO Remove induction variables from omp.wsloop.
152+
omp.wsloop for (%iv) : index = (%lb) to (%ub) step (%step) {
153+
// CHECK: omp.loopnest
154+
// CHECK-SAME: (%{{.*}}) : index =
155+
// CHECK-SAME: (%{{.*}}) to (%{{.*}}) inclusive step (%{{.*}})
156+
"omp.loopnest" (%lb, %ub, %step) ({
157+
^bb0(%iv2: index):
158+
omp.yield
159+
}) {inclusive} : (index, index, index) -> ()
160+
omp.yield
161+
}
162+
163+
// TODO Remove induction variables from omp.wsloop.
164+
omp.wsloop for (%iv) : index = (%lb) to (%ub) step (%step) {
165+
// CHECK: omp.loopnest
166+
// CHECK-SAME: (%{{.*}}, %{{.*}}) : index =
167+
// CHECK-SAME: (%{{.*}}, %{{.*}}) to (%{{.*}}, %{{.*}}) step (%{{.*}}, %{{.*}})
168+
"omp.loopnest" (%lb, %lb, %ub, %ub, %step, %step) ({
169+
^bb0(%iv2: index, %iv3: index):
170+
omp.yield
171+
}) : (index, index, index, index, index, index) -> ()
172+
omp.yield
173+
}
174+
175+
return
176+
}
177+
178+
// CHECK-LABEL: omp_loopnest_pretty
179+
func.func @omp_loopnest_pretty(%lb : index, %ub : index, %step : index) -> () {
180+
181+
// TODO Remove induction variables from omp.wsloop.
182+
omp.wsloop for (%iv) : index = (%lb) to (%ub) step (%step) {
183+
// CHECK: omp.loopnest
184+
// CHECK-SAME: (%{{.*}}) : index =
185+
// CHECK-SAME: (%{{.*}}) to (%{{.*}}) step (%{{.*}})
186+
omp.loopnest (%iv2) : index = (%lb) to (%ub) step (%step) {
187+
omp.yield
188+
}
189+
omp.yield
190+
}
191+
192+
// TODO Remove induction variables from omp.wsloop.
193+
omp.wsloop for (%iv) : index = (%lb) to (%ub) step (%step) {
194+
// CHECK: omp.loopnest
195+
// CHECK-SAME: (%{{.*}}) : index =
196+
// CHECK-SAME: (%{{.*}}) to (%{{.*}}) inclusive step (%{{.*}})
197+
omp.loopnest (%iv2) : index = (%lb) to (%ub) inclusive step (%step) {
198+
omp.yield
199+
}
200+
omp.yield
201+
}
202+
203+
// TODO Remove induction variables from omp.wsloop.
204+
omp.wsloop for (%iv) : index = (%lb) to (%ub) step (%step) {
205+
// CHECK: omp.loopnest
206+
// CHECK-SAME: (%{{.*}}) : index =
207+
// CHECK-SAME: (%{{.*}}, %{{.*}}) to (%{{.*}}, %{{.*}}) step (%{{.*}}, %{{.*}})
208+
omp.loopnest (%iv2, %iv3) : index = (%lb, %lb) to (%ub, %ub) step (%step, %step) {
209+
omp.yield
210+
}
211+
omp.yield
212+
}
213+
214+
return
215+
}
216+
136217
// CHECK-LABEL: omp_wsloop
137218
func.func @omp_wsloop(%lb : index, %ub : index, %step : index, %data_var : memref<i32>, %linear_var : i32, %chunk_var : i32) -> () {
138219

0 commit comments

Comments
 (0)