Skip to content

Commit 728806b

Browse files
committed
[MLIR][OpenMP] Add omp.private op
This PR adds a new op to the OpenMP dialect: `PrivateClauseOp`. This op will be later used to model `[first]private` clauses for differnt OpenMP directives. This is part of productizing the "delayed privatization" PoC wich can be found in #79862.
1 parent e60c4b6 commit 728806b

File tree

4 files changed

+194
-1
lines changed

4 files changed

+194
-1
lines changed

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

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
include "mlir/IR/EnumAttr.td"
1818
include "mlir/IR/OpBase.td"
19+
include "mlir/Interfaces/FunctionInterfaces.td"
1920
include "mlir/Interfaces/SideEffectInterfaces.td"
2021
include "mlir/Interfaces/ControlFlowInterfaces.td"
2122
include "mlir/IR/SymbolInterfaces.td"
@@ -133,6 +134,84 @@ def DeclareTargetAttr : OpenMP_Attr<"DeclareTarget", "declaretarget"> {
133134
let assemblyFormat = "`<` struct(params) `>`";
134135
}
135136

137+
//===----------------------------------------------------------------------===//
138+
// 2.19.4 Data-Sharing Attribute Clauses
139+
//===----------------------------------------------------------------------===//
140+
141+
def PrivateClauseOp : OpenMP_Op<"private", [
142+
IsolatedFromAbove, FunctionOpInterface
143+
]> {
144+
let summary = "Outline [first]private logic in a separate op.";
145+
let description = [{
146+
Using this operation, the dialect can model the data-sharing attributes of
147+
`private` and `firstprivate` variables on the IR level. This means that of
148+
"eagerly" privatizing variables in the frontend, we can instead model which
149+
variables should be privatized and only materialze the privatization when
150+
necessary; e.g. directly before lowring to LLVM IR.
151+
152+
Examples:
153+
---------
154+
* `private(x)` would be emitted as:
155+
```mlir
156+
omp.private @x.privatizer : (!fir.ref<i32>) -> !fir.ref<i32> {
157+
^bb0(%arg0: !fir.ref<i32>):
158+
%0 = fir.alloca i32
159+
omp.yield(%0 : !fir.ref<i32>)
160+
}
161+
```
162+
163+
* `firstprivate(x)` would be emitted as:
164+
```mlir
165+
omp.private @x.privatizer : (!fir.ref<i32>) -> !fir.ref<i32> {
166+
^bb0(%arg0: !fir.ref<i32>):
167+
%0 = fir.alloca i32
168+
%1 = fir.load %arg0 : !fir.ref<i32>
169+
fir.store %1 to %0 : !fir.ref<i32>
170+
omp.yield(%0 : !fir.ref<i32>)
171+
}
172+
```
173+
174+
However, the body of the `omp.private` op really depends on the code-gen
175+
done by the emitting frontend. There are no restrictions on the body except
176+
for having the yield a value of the same type as the operand.
177+
178+
Instances of this op would then be used by ops that model directives that
179+
accept data-sharing attribute clauses.
180+
}];
181+
182+
let arguments = (ins SymbolNameAttr:$sym_name,
183+
TypeAttrOf<FunctionType>:$function_type);
184+
185+
let regions = (region AnyRegion:$body);
186+
187+
let builders = [OpBuilder<(ins
188+
"::mlir::Type":$privateVarType,
189+
"::llvm::StringRef":$privatizerName
190+
)>];
191+
192+
let assemblyFormat = [{
193+
$sym_name `:` $function_type $body attr-dict
194+
}];
195+
196+
let extraClassDeclaration = [{
197+
::mlir::Region *getCallableRegion() {
198+
return &getBody();
199+
}
200+
201+
/// Returns the argument types of this function.
202+
ArrayRef<Type> getArgumentTypes() {
203+
return getFunctionType().getInputs();
204+
}
205+
206+
/// Returns the result types of this function.
207+
ArrayRef<Type> getResultTypes() {
208+
return getFunctionType().getResults();
209+
}
210+
}];
211+
212+
let hasVerifier = 1;
213+
}
214+
136215
//===----------------------------------------------------------------------===//
137216
// 2.6 parallel Construct
138217
//===----------------------------------------------------------------------===//
@@ -612,7 +691,7 @@ def SimdLoopOp : OpenMP_Op<"simdloop", [AttrSizedOperandSegments,
612691
def YieldOp : OpenMP_Op<"yield",
613692
[Pure, ReturnLike, Terminator,
614693
ParentOneOf<["WsLoopOp", "ReductionDeclareOp",
615-
"AtomicUpdateOp", "SimdLoopOp"]>]> {
694+
"AtomicUpdateOp", "SimdLoopOp", "PrivateClauseOp"]>]> {
616695
let summary = "loop yield and termination operation";
617696
let description = [{
618697
"omp.yield" yields SSA values from the OpenMP dialect op region and

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,6 +1594,61 @@ LogicalResult DataBoundsOp::verify() {
15941594
return success();
15951595
}
15961596

1597+
LogicalResult PrivateClauseOp::verify() {
1598+
Region &body = getBody();
1599+
auto argumentTypes = getArgumentTypes();
1600+
auto resultTypes = getResultTypes();
1601+
1602+
if (argumentTypes.empty()) {
1603+
return emitError() << "'" << getOperationName()
1604+
<< "' must accept at least one argument.";
1605+
}
1606+
1607+
if (resultTypes.empty()) {
1608+
return emitError() << "'" << getOperationName()
1609+
<< "' must return at least one result.";
1610+
}
1611+
1612+
for (Block &block : body) {
1613+
if (block.empty() || !block.mightHaveTerminator())
1614+
return mlir::emitError(block.empty() ? getLoc() : block.back().getLoc())
1615+
<< "expected all blocks to have terminators.";
1616+
1617+
Operation *terminator = block.getTerminator();
1618+
1619+
if (!terminator->hasSuccessors() && !llvm::isa<YieldOp>(terminator))
1620+
return mlir::emitError(terminator->getLoc())
1621+
<< "expected exit block terminator to be an `omp.yield` op.";
1622+
1623+
YieldOp yieldOp = llvm::cast<YieldOp>(terminator);
1624+
auto yieldedTypes = yieldOp.getResults().getTypes();
1625+
1626+
if (yieldedTypes.empty())
1627+
return mlir::emitError(yieldOp.getLoc())
1628+
<< "'" << getOperationName() << "' must yield a value.";
1629+
1630+
bool yieldIsValid = [&]() {
1631+
if (yieldedTypes.size() != resultTypes.size()) {
1632+
return false;
1633+
}
1634+
for (size_t typeIdx = 0; typeIdx < yieldedTypes.size(); ++typeIdx) {
1635+
if (yieldedTypes[typeIdx] != resultTypes[typeIdx]) {
1636+
return false;
1637+
}
1638+
}
1639+
1640+
return true;
1641+
}();
1642+
1643+
if (!yieldIsValid)
1644+
return mlir::emitError(yieldOp.getLoc())
1645+
<< "Invalid yielded value. Expected type: " << resultTypes
1646+
<< ", got: " << yieldedTypes;
1647+
}
1648+
1649+
return success();
1650+
}
1651+
15971652
#define GET_ATTRDEF_CLASSES
15981653
#include "mlir/Dialect/OpenMP/OpenMPOpsAttributes.cpp.inc"
15991654

mlir/test/Dialect/OpenMP/invalid.mlir

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1738,3 +1738,49 @@ func.func @omp_distribute(%data_var : memref<i32>) -> () {
17381738
"omp.terminator"() : () -> ()
17391739
}) : (memref<i32>) -> ()
17401740
}
1741+
1742+
// -----
1743+
1744+
omp.private @x.privatizer : (i32) -> i32 {
1745+
^bb0(%arg0: i32):
1746+
%0 = arith.constant 0.0 : f32
1747+
// expected-error @below {{Invalid yielded value. Expected type: 'i32', got: 'f32'}}
1748+
omp.yield(%0 : f32)
1749+
}
1750+
1751+
// -----
1752+
1753+
omp.private @x.privatizer : (i32) -> i32 {
1754+
^bb0(%arg0: i32):
1755+
// expected-error @below {{'omp.private' must yield a value.}}
1756+
omp.yield
1757+
}
1758+
1759+
// -----
1760+
1761+
// expected-error @below {{'omp.private' must accept at least one argument.}}
1762+
omp.private @x.privatizer : () -> i32 {
1763+
^bb0:
1764+
omp.yield
1765+
}
1766+
1767+
// -----
1768+
1769+
// expected-error @below {{'omp.private' must return at least one result.}}
1770+
omp.private @x.privatizer : (i32) -> () {
1771+
}
1772+
1773+
// -----
1774+
1775+
// expected-error @below {{expected all blocks to have terminators.}}
1776+
omp.private @x.privatizer : (i32) -> i32 {
1777+
^bb0(%arg0: i32):
1778+
}
1779+
1780+
// -----
1781+
1782+
omp.private @x.privatizer : (i32) -> i32 {
1783+
^bb0(%arg0: i32):
1784+
// expected-error @below {{expected exit block terminator to be an `omp.yield` op.}}
1785+
omp.terminator
1786+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// RUN: fir-opt -verify-diagnostics %s | fir-opt | FileCheck %s
2+
3+
// CHECK: omp.private @x.privatizer : (!fir.ref<i32>) -> !fir.ref<i32> {
4+
omp.private @x.privatizer : (!fir.ref<i32>) -> !fir.ref<i32> {
5+
// CHECK: ^bb0(%arg0: {{.*}}):
6+
^bb0(%arg0: !fir.ref<i32>):
7+
8+
// CHECK: %0 = fir.alloca i32
9+
%0 = fir.alloca i32
10+
// CHECK: omp.yield(%0 : !fir.ref<i32>)
11+
omp.yield(%0 : !fir.ref<i32>)
12+
}
13+

0 commit comments

Comments
 (0)