Skip to content

Commit 0e63672

Browse files
authored
[MLIR][OpenMP] Add omp.loop_nest operation (#87083)
This patch introduces an operation intended to hold loop information associated to the `omp.distribute`, `omp.simdloop`, `omp.taskloop` and `omp.wsloop` operations. This is a stopgap solution to unblock work on transitioning these operations to becoming wrappers, as discussed in [this RFC](https://discourse.llvm.org/t/rfc-representing-combined-composite-constructs-in-the-openmp-dialect/76986). Long-term, this operation will likely be replaced by `omp.canonical_loop`, which is being designed to address missing support for loop transformations, etc.
1 parent 5d6b009 commit 0e63672

File tree

4 files changed

+252
-1
lines changed

4 files changed

+252
-1
lines changed

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

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,70 @@ def SingleOp : OpenMP_Op<"single", [AttrSizedOperandSegments]> {
524524
let hasVerifier = 1;
525525
}
526526

527+
//===----------------------------------------------------------------------===//
528+
// Loop Nest
529+
//===----------------------------------------------------------------------===//
530+
531+
def LoopNestOp : OpenMP_Op<"loop_nest", [SameVariadicOperandSize,
532+
AllTypesMatch<["lowerBound", "upperBound", "step"]>,
533+
ParentOneOf<["DistributeOp", "SimdLoopOp", "TaskloopOp",
534+
"WsloopOp"]>,
535+
RecursiveMemoryEffects]> {
536+
let summary = "rectangular loop nest";
537+
let description = [{
538+
This operation represents a collapsed rectangular loop nest. For each
539+
rectangular loop of the nest represented by an instance of this operation,
540+
lower and upper bounds, as well as a step variable, must be defined.
541+
542+
The lower and upper bounds specify a half-open range: the range includes the
543+
lower bound but does not include the upper bound. If the `inclusive`
544+
attribute is specified then the upper bound is also included.
545+
546+
The body region can contain any number of blocks. The region is terminated
547+
by an `omp.yield` instruction without operands. The induction variables,
548+
represented as entry block arguments to the loop nest operation's single
549+
region, match the types of the `lowerBound`, `upperBound` and `step`
550+
arguments.
551+
552+
```mlir
553+
omp.loop_nest (%i1, %i2) : i32 = (%c0, %c0) to (%c10, %c10) step (%c1, %c1) {
554+
%a = load %arrA[%i1, %i2] : memref<?x?xf32>
555+
%b = load %arrB[%i1, %i2] : memref<?x?xf32>
556+
%sum = arith.addf %a, %b : f32
557+
store %sum, %arrC[%i1, %i2] : memref<?x?xf32>
558+
omp.yield
559+
}
560+
```
561+
562+
This is a temporary simplified definition of a loop based on existing OpenMP
563+
loop operations intended to serve as a stopgap solution until the long-term
564+
representation of canonical loops is defined. Specifically, this operation
565+
is intended to serve as a unique source for loop information during the
566+
transition to making `omp.distribute`, `omp.simdloop`, `omp.taskloop` and
567+
`omp.wsloop` wrapper operations. It is not intended to help with the
568+
addition of support for loop transformations, non-rectangular loops and
569+
non-perfectly nested loops.
570+
}];
571+
572+
let arguments = (ins Variadic<IntLikeType>:$lowerBound,
573+
Variadic<IntLikeType>:$upperBound,
574+
Variadic<IntLikeType>:$step,
575+
UnitAttr:$inclusive);
576+
577+
let regions = (region AnyRegion:$region);
578+
579+
let extraClassDeclaration = [{
580+
/// Returns the number of loops in the loop nest.
581+
unsigned getNumLoops() { return getLowerBound().size(); }
582+
583+
/// Returns the induction variables of the loop nest.
584+
ArrayRef<BlockArgument> getIVs() { return getRegion().getArguments(); }
585+
}];
586+
587+
let hasCustomAssemblyFormat = 1;
588+
let hasVerifier = 1;
589+
}
590+
527591
//===----------------------------------------------------------------------===//
528592
// 2.9.2 Workshare Loop Construct
529593
//===----------------------------------------------------------------------===//
@@ -743,7 +807,7 @@ def SimdLoopOp : OpenMP_Op<"simdloop", [AttrSizedOperandSegments,
743807

744808
def YieldOp : OpenMP_Op<"yield",
745809
[Pure, ReturnLike, Terminator,
746-
ParentOneOf<["WsloopOp", "DeclareReductionOp",
810+
ParentOneOf<["LoopNestOp", "WsloopOp", "DeclareReductionOp",
747811
"AtomicUpdateOp", "SimdLoopOp", "PrivateClauseOp"]>]> {
748812
let summary = "loop yield and termination operation";
749813
let description = [{

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1862,6 +1862,77 @@ LogicalResult TaskloopOp::verify() {
18621862
return success();
18631863
}
18641864

1865+
//===----------------------------------------------------------------------===//
1866+
// LoopNestOp
1867+
//===----------------------------------------------------------------------===//
1868+
1869+
ParseResult LoopNestOp::parse(OpAsmParser &parser, OperationState &result) {
1870+
// Parse an opening `(` followed by induction variables followed by `)`
1871+
SmallVector<OpAsmParser::Argument> ivs;
1872+
SmallVector<OpAsmParser::UnresolvedOperand> lbs, ubs;
1873+
Type loopVarType;
1874+
if (parser.parseArgumentList(ivs, OpAsmParser::Delimiter::Paren) ||
1875+
parser.parseColonType(loopVarType) ||
1876+
// Parse loop bounds.
1877+
parser.parseEqual() ||
1878+
parser.parseOperandList(lbs, ivs.size(), OpAsmParser::Delimiter::Paren) ||
1879+
parser.parseKeyword("to") ||
1880+
parser.parseOperandList(ubs, ivs.size(), OpAsmParser::Delimiter::Paren))
1881+
return failure();
1882+
1883+
for (auto &iv : ivs)
1884+
iv.type = loopVarType;
1885+
1886+
// Parse "inclusive" flag.
1887+
if (succeeded(parser.parseOptionalKeyword("inclusive")))
1888+
result.addAttribute("inclusive",
1889+
UnitAttr::get(parser.getBuilder().getContext()));
1890+
1891+
// Parse step values.
1892+
SmallVector<OpAsmParser::UnresolvedOperand> steps;
1893+
if (parser.parseKeyword("step") ||
1894+
parser.parseOperandList(steps, ivs.size(), OpAsmParser::Delimiter::Paren))
1895+
return failure();
1896+
1897+
// Parse the body.
1898+
Region *region = result.addRegion();
1899+
if (parser.parseRegion(*region, ivs))
1900+
return failure();
1901+
1902+
// Resolve operands.
1903+
if (parser.resolveOperands(lbs, loopVarType, result.operands) ||
1904+
parser.resolveOperands(ubs, loopVarType, result.operands) ||
1905+
parser.resolveOperands(steps, loopVarType, result.operands))
1906+
return failure();
1907+
1908+
// Parse the optional attribute list.
1909+
return parser.parseOptionalAttrDict(result.attributes);
1910+
}
1911+
1912+
void LoopNestOp::print(OpAsmPrinter &p) {
1913+
Region &region = getRegion();
1914+
auto args = region.getArguments();
1915+
p << " (" << args << ") : " << args[0].getType() << " = (" << getLowerBound()
1916+
<< ") to (" << getUpperBound() << ") ";
1917+
if (getInclusive())
1918+
p << "inclusive ";
1919+
p << "step (" << getStep() << ") ";
1920+
p.printRegion(region, /*printEntryBlockArgs=*/false);
1921+
}
1922+
1923+
LogicalResult LoopNestOp::verify() {
1924+
if (getLowerBound().size() != getIVs().size())
1925+
return emitOpError() << "number of range arguments and IVs do not match";
1926+
1927+
for (auto [lb, iv] : llvm::zip_equal(getLowerBound(), getIVs())) {
1928+
if (lb.getType() != iv.getType())
1929+
return emitOpError()
1930+
<< "range argument type does not match corresponding IV type";
1931+
}
1932+
1933+
return success();
1934+
}
1935+
18651936
//===----------------------------------------------------------------------===//
18661937
// WsloopOp
18671938
//===----------------------------------------------------------------------===//

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.loop_nest (%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.loop_nest" (%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.loop_nest" (%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: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,85 @@ func.func @omp_parallel_pretty(%data_var : memref<i32>, %if_cond : i1, %num_thre
133133
return
134134
}
135135

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

0 commit comments

Comments
 (0)