Skip to content

Commit 5e25037

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 b85fe40 commit 5e25037

File tree

4 files changed

+248
-1
lines changed

4 files changed

+248
-1
lines changed

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

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,95 @@ def DeclareTargetAttr : OpenMP_Attr<"DeclareTarget", "declaretarget"> {
133133
let assemblyFormat = "`<` struct(params) `>`";
134134
}
135135

136+
//===----------------------------------------------------------------------===//
137+
// 2.19.4 Data-Sharing Attribute Clauses
138+
//===----------------------------------------------------------------------===//
139+
140+
def DataSharingTypePrivate : I32EnumAttrCase<"Private", 0, "private">;
141+
def DataSharingTypeFirstPrivate : I32EnumAttrCase<"FirstPrivate", 1, "firstprivate">;
142+
143+
def DataSharingClauseType : I32EnumAttr<
144+
"DataSharingClauseType",
145+
"Type of a data-sharing clause",
146+
[DataSharingTypePrivate, DataSharingTypeFirstPrivate]> {
147+
let genSpecializedAttr = 0;
148+
let cppNamespace = "::mlir::omp";
149+
}
150+
151+
def DataSharingClauseTypeAttr : EnumAttr<
152+
OpenMP_Dialect, DataSharingClauseType, "data_sharing_type"> {
153+
let assemblyFormat = "`{` `type` `=` $value `}`";
154+
}
155+
156+
def PrivateClauseOp : OpenMP_Op<"private", [
157+
IsolatedFromAbove
158+
]> {
159+
let summary = "Outline [first]private logic in a separate op.";
160+
let description = [{
161+
Using this operation, the dialect can model the data-sharing attributes of
162+
`private` and `firstprivate` variables on the IR level. This means that of
163+
"eagerly" privatizing variables in the frontend, we can instead model which
164+
variables should be privatized and only materialze the privatization when
165+
necessary; e.g. directly before lowering to LLVM IR.
166+
167+
Examples:
168+
---------
169+
* `private(x)` would be emitted as:
170+
```mlir
171+
omp.private {type = private} @x.privatizer : !fir.ref<i32> (alloc {
172+
^bb0(%arg0: !fir.ref<i32>):
173+
%0 = ... allocate proper memory for the private clone ...
174+
omp.yield(%0 : !fir.ref<i32>)
175+
})
176+
```
177+
178+
* `firstprivate(x)` would be emitted as:
179+
```mlir
180+
omp.private {type = firstprivate} @y.privatizer : !fir.ref<i32> (alloc {
181+
^bb0(%arg0: !fir.ref<i32>):
182+
%0 = ... allocate proper memory for the private clone ...
183+
omp.yield(%0 : !fir.ref<i32>)
184+
} copy {
185+
^bb0(%arg0: !fir.ref<i32>, %arg1: !fir.ref<i32>):
186+
// %arg0 is the original host variable. Same as for `alloc`.
187+
// %arg1 represents the memory allocated in `alloc`.
188+
... copy from host to the privatized clone ....
189+
omp.yield(%arg1 : !fir.ref<i32>)
190+
})
191+
```
192+
193+
However, the body of the `omp.private` op really depends on the code-gen
194+
done by the emitting frontend. There are no restrictions on the body except
195+
for:
196+
- The `alloc` region has a single argument.
197+
- The `copy` region has 2 arguments.
198+
- Both regions are existed by `omp.yield` ops.
199+
The above restrictions and other obvious restrictions (e.g. verifying the
200+
type of yielded values) are verified by the custom op verifier. The actual
201+
contents of the blocks inside both regions are not verified.
202+
203+
Instances of this op would then be used by ops that model directives that
204+
accept data-sharing attribute clauses.
205+
}];
206+
207+
let arguments = (ins SymbolNameAttr:$sym_name,
208+
TypeAttrOf<AnyType>:$sym_type,
209+
DataSharingClauseTypeAttr:$data_sharing_type);
210+
211+
let regions = (region MinSizedRegion<1>:$alloc_region,
212+
AnyRegion:$copy_region);
213+
214+
let assemblyFormat = [{
215+
$data_sharing_type $sym_name `:` $sym_type `(`
216+
`alloc` $alloc_region
217+
(`copy` $copy_region^)?
218+
`)`
219+
attr-dict
220+
}];
221+
222+
let hasVerifier = 1;
223+
}
224+
136225
//===----------------------------------------------------------------------===//
137226
// 2.6 parallel Construct
138227
//===----------------------------------------------------------------------===//
@@ -612,7 +701,7 @@ def SimdLoopOp : OpenMP_Op<"simdloop", [AttrSizedOperandSegments,
612701
def YieldOp : OpenMP_Op<"yield",
613702
[Pure, ReturnLike, Terminator,
614703
ParentOneOf<["WsLoopOp", "ReductionDeclareOp",
615-
"AtomicUpdateOp", "SimdLoopOp"]>]> {
704+
"AtomicUpdateOp", "SimdLoopOp", "PrivateClauseOp"]>]> {
616705
let summary = "loop yield and termination operation";
617706
let description = [{
618707
"omp.yield" yields SSA values from the OpenMP dialect op region and

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

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

1597+
LogicalResult PrivateClauseOp::verify() {
1598+
Type symType = getSymType();
1599+
1600+
auto verifyTerminator = [&](Operation *terminator) -> LogicalResult {
1601+
if (!terminator->hasSuccessors() && !llvm::isa<YieldOp>(terminator))
1602+
return mlir::emitError(terminator->getLoc())
1603+
<< "expected exit block terminator to be an `omp.yield` op.";
1604+
1605+
YieldOp yieldOp = llvm::cast<YieldOp>(terminator);
1606+
TypeRange yieldedTypes = yieldOp.getResults().getTypes();
1607+
1608+
if (yieldedTypes.size() == 1 && yieldedTypes.front() == symType)
1609+
return success();
1610+
1611+
auto error = mlir::emitError(yieldOp.getLoc())
1612+
<< "Invalid yielded value. Expected type: " << symType
1613+
<< ", got: ";
1614+
1615+
if (yieldedTypes.empty())
1616+
error << "None";
1617+
else
1618+
error << yieldedTypes;
1619+
1620+
return error;
1621+
};
1622+
1623+
auto verifyRegion = [&](Region &region, unsigned expectedNumArgs,
1624+
StringRef regionName) -> LogicalResult {
1625+
assert(!region.empty());
1626+
1627+
if (region.getNumArguments() != expectedNumArgs)
1628+
return mlir::emitError(region.getLoc())
1629+
<< "`" << regionName << "`: "
1630+
<< "expected " << expectedNumArgs
1631+
<< " region arguments, got: " << region.getNumArguments();
1632+
1633+
for (Block &block : region) {
1634+
if (block.empty() || !block.mightHaveTerminator())
1635+
return mlir::emitError(block.empty() ? getLoc() : block.back().getLoc())
1636+
<< "expected all blocks to have terminators.";
1637+
1638+
if (failed(verifyTerminator(block.getTerminator())))
1639+
return failure();
1640+
}
1641+
1642+
return success();
1643+
};
1644+
1645+
if (failed(verifyRegion(getAllocRegion(), /*expectedNumArgs=*/1, "alloc")))
1646+
return failure();
1647+
1648+
DataSharingClauseType dsType = getDataSharingType();
1649+
1650+
if (dsType == DataSharingClauseType::Private && !getCopyRegion().empty())
1651+
return emitError("`private` clauses require only an `alloc` region.");
1652+
1653+
if (dsType == DataSharingClauseType::FirstPrivate && getCopyRegion().empty())
1654+
return emitError(
1655+
"`firstprivate` clauses require both `alloc` and `copy` regions.");
1656+
1657+
if (dsType == DataSharingClauseType::FirstPrivate &&
1658+
failed(verifyRegion(getCopyRegion(), /*expectedNumArgs=*/2, "copy")))
1659+
return failure();
1660+
1661+
return success();
1662+
}
1663+
15971664
#define GET_ATTRDEF_CLASSES
15981665
#include "mlir/Dialect/OpenMP/OpenMPOpsAttributes.cpp.inc"
15991666

mlir/test/Dialect/OpenMP/invalid.mlir

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1738,3 +1738,73 @@ func.func @omp_distribute(%data_var : memref<i32>) -> () {
17381738
"omp.terminator"() : () -> ()
17391739
}) : (memref<i32>) -> ()
17401740
}
1741+
1742+
// -----
1743+
1744+
omp.private {type = private} @x.privatizer : i32 (alloc {
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 {type = private} @x.privatizer : i32 (alloc {
1754+
^bb0(%arg0: i32):
1755+
// expected-error @below {{Invalid yielded value. Expected type: 'i32', got: None}}
1756+
omp.yield
1757+
})
1758+
1759+
// -----
1760+
1761+
// expected-error @below {{expected all blocks to have terminators.}}
1762+
omp.private {type = private} @x.privatizer : i32 (alloc {
1763+
^bb0(%arg0: i32):
1764+
})
1765+
1766+
// -----
1767+
1768+
omp.private {type = private} @x.privatizer : i32 (alloc {
1769+
^bb0(%arg0: i32):
1770+
// expected-error @below {{expected exit block terminator to be an `omp.yield` op.}}
1771+
omp.terminator
1772+
})
1773+
1774+
// -----
1775+
1776+
// expected-error @below {{`alloc`: expected 1 region arguments, got: 2}}
1777+
omp.private {type = private} @x.privatizer : f32 (alloc {
1778+
^bb0(%arg0: f32, %arg1: f32):
1779+
omp.yield(%arg0 : f32)
1780+
})
1781+
1782+
// -----
1783+
1784+
// expected-error @below {{`copy`: expected 2 region arguments, got: 1}}
1785+
omp.private {type = firstprivate} @x.privatizer : f32 (alloc {
1786+
^bb0(%arg0: f32):
1787+
omp.yield(%arg0 : f32)
1788+
} copy {
1789+
^bb0(%arg0: f32):
1790+
omp.yield(%arg0 : f32)
1791+
})
1792+
1793+
// -----
1794+
1795+
// expected-error @below {{`private` clauses require only an `alloc` region.}}
1796+
omp.private {type = private} @x.privatizer : f32 (alloc {
1797+
^bb0(%arg0: f32):
1798+
omp.yield(%arg0 : f32)
1799+
} copy {
1800+
^bb0(%arg0: f32, %arg1 : f32):
1801+
omp.yield(%arg0 : f32)
1802+
})
1803+
1804+
// -----
1805+
1806+
// expected-error @below {{`firstprivate` clauses require both `alloc` and `copy` regions.}}
1807+
omp.private {type = firstprivate} @x.privatizer : f32 (alloc {
1808+
^bb0(%arg0: f32):
1809+
omp.yield(%arg0 : f32)
1810+
})
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: fir-opt -verify-diagnostics %s | fir-opt | FileCheck %s
2+
3+
// CHECK: omp.private {type = private} @x.privatizer : !fir.ref<i32>(alloc {
4+
omp.private {type = private} @x.privatizer : !fir.ref<i32> (alloc {
5+
// CHECK: ^bb0(%arg0: {{.*}}):
6+
^bb0(%arg0: !fir.ref<i32>):
7+
omp.yield(%arg0 : !fir.ref<i32>)
8+
})
9+
10+
// CHECK: omp.private {type = firstprivate} @y.privatizer : !fir.ref<i32>(alloc {
11+
omp.private {type = firstprivate} @y.privatizer : !fir.ref<i32> (alloc {
12+
// CHECK: ^bb0(%arg0: {{.*}}):
13+
^bb0(%arg0: !fir.ref<i32>):
14+
omp.yield(%arg0 : !fir.ref<i32>)
15+
// CHECK: } copy {
16+
} copy {
17+
// CHECK: ^bb0(%arg0: {{.*}}, %arg1: {{.*}}):
18+
^bb0(%arg0: !fir.ref<i32>, %arg1: !fir.ref<i32>):
19+
omp.yield(%arg0 : !fir.ref<i32>)
20+
})
21+

0 commit comments

Comments
 (0)