Skip to content

Commit 649e18f

Browse files
committed
[MLIR][OpenMP] Add simplified omp.canonical_loop
This patch introduces the `omp.canonical_loop` MLIR operation, which contains the information of a 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.
1 parent 14487b4 commit 649e18f

File tree

4 files changed

+249
-1
lines changed

4 files changed

+249
-1
lines changed

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

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

509+
//===----------------------------------------------------------------------===//
510+
// 2.9.1 Canonical Loop
511+
//===----------------------------------------------------------------------===//
512+
513+
def CanonicalLoopOp : OpenMP_Op<"canonical_loop", [SameVariadicOperandSize,
514+
AllTypesMatch<["lowerBound", "upperBound", "step"]>,
515+
ParentOneOf<["DistributeOp", "SimdLoopOp", "TaskLoopOp",
516+
"WsLoopOp"]>,
517+
RecursiveMemoryEffects]> {
518+
let summary = "canonical loop";
519+
let description = [{
520+
All loops that conform to OpenMP's definition of a canonical loop can be
521+
simplified to a CanonicalLoopOp. In particular, there are no loop-carried
522+
variables and the number of iterations it will execute is known before the
523+
operation. This allows e.g. to determine the number of threads and chunks
524+
the iterations space is split into before executing any iteration. More
525+
restrictions may apply in cases such as (collapsed) loop nests, doacross
526+
loops, etc.
527+
528+
The lower and upper bounds specify a half-open range: the range includes the
529+
lower bound but does not include the upper bound. If the `inclusive`
530+
attribute is specified then the upper bound is also included.
531+
532+
The body region can contain any number of blocks. The region is terminated
533+
by "omp.yield" instruction without operands. The induction variables,
534+
represented as entry block arguments to the canonical loop operation's
535+
single region, match the types of the `lowerBound`, `upperBound` and `step`
536+
arguments.
537+
538+
```mlir
539+
omp.canonical_loop (%i1, %i2) : i32 = (%c0, %c0) to (%c10, %c10) step (%c1, %c1) {
540+
%a = load %arrA[%i1, %i2] : memref<?x?xf32>
541+
%b = load %arrB[%i1, %i2] : memref<?x?xf32>
542+
%sum = arith.addf %a, %b : f32
543+
store %sum, %arrC[%i1, %i2] : memref<?x?xf32>
544+
omp.yield
545+
}
546+
```
547+
548+
This is a temporary simplified definition of canonical loop based on
549+
existing OpenMP loop operations intended to serve as a stopgap solution
550+
until discussion over its long-term representation reaches a conclusion.
551+
Specifically, this approach is not intended to help with the addition of
552+
support for loop transformations.
553+
}];
554+
555+
let arguments = (ins Variadic<IntLikeType>:$lowerBound,
556+
Variadic<IntLikeType>:$upperBound,
557+
Variadic<IntLikeType>:$step,
558+
UnitAttr:$inclusive);
559+
560+
let regions = (region AnyRegion:$region);
561+
562+
let extraClassDeclaration = [{
563+
/// Returns the number of loops in the canonical loop nest.
564+
unsigned getNumLoops() { return getLowerBound().size(); }
565+
566+
/// Returns the induction variables of the canonical loop nest.
567+
ArrayRef<BlockArgument> getIVs() { return getRegion().getArguments(); }
568+
}];
569+
570+
let hasCustomAssemblyFormat = 1;
571+
let hasVerifier = 1;
572+
}
573+
509574
//===----------------------------------------------------------------------===//
510575
// 2.9.2 Workshare Loop Construct
511576
//===----------------------------------------------------------------------===//
@@ -714,7 +779,7 @@ def SimdLoopOp : OpenMP_Op<"simdloop", [AttrSizedOperandSegments,
714779

715780
def YieldOp : OpenMP_Op<"yield",
716781
[Pure, ReturnLike, Terminator,
717-
ParentOneOf<["WsLoopOp", "ReductionDeclareOp",
782+
ParentOneOf<["CanonicalLoopOp", "WsLoopOp", "ReductionDeclareOp",
718783
"AtomicUpdateOp", "SimdLoopOp", "PrivateClauseOp"]>]> {
719784
let summary = "loop yield and termination operation";
720785
let description = [{

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

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

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

mlir/test/Dialect/OpenMP/invalid.mlir

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,41 @@ 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.canonical_loop (%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+
omp.wsloop for (%iv) : index = (%lb) to (%ub) step (%step) {
101+
// expected-error@+1 {{range argument type does not match corresponding IV type}}
102+
"omp.canonical_loop" (%lb, %ub, %step) ({
103+
^bb0(%iv2: i32):
104+
omp.yield
105+
}) : (index, index, index) -> ()
106+
omp.yield
107+
}
108+
}
109+
110+
// -----
111+
112+
func.func @iv_number_mismatch(%lb : index, %ub : index, %step : index) {
113+
omp.wsloop for (%iv) : index = (%lb) to (%ub) step (%step) {
114+
// expected-error@+1 {{number of range arguments and IVs do not match}}
115+
"omp.canonical_loop" (%lb, %ub, %step) ({
116+
^bb0(%iv1 : index, %iv2 : index):
117+
omp.yield
118+
}) : (index, index, index) -> ()
119+
omp.yield
120+
}
121+
}
122+
123+
// -----
124+
90125
func.func @inclusive_not_a_clause(%lb : index, %ub : index, %step : index) {
91126
// expected-error @below {{expected 'for'}}
92127
omp.wsloop nowait inclusive

mlir/test/Dialect/OpenMP/ops.mlir

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

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

0 commit comments

Comments
 (0)