diff options
author | Kareem Ergawy <kareem.ergawy@amd.com> | 2024-02-13 05:10:18 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-13 05:10:18 +0100 |
commit | 070fad422834faab1f0ffc90e21e25d2f524b17b (patch) | |
tree | 5ae6bf7bc532837d8e37eb3bd529ad4cafbcd680 | |
parent | c43ad6c0fddac0bbed5e881801dd2bc2f9eeba2d (diff) | |
download | llvm-070fad422834faab1f0ffc90e21e25d2f524b17b.zip llvm-070fad422834faab1f0ffc90e21e25d2f524b17b.tar.gz llvm-070fad422834faab1f0ffc90e21e25d2f524b17b.tar.bz2 |
[MLIR][OpenMP] Add `omp.private` op (#80955)
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 which can
be found in #79862.
-rw-r--r-- | mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td | 93 | ||||
-rw-r--r-- | mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp | 67 | ||||
-rw-r--r-- | mlir/test/Dialect/OpenMP/invalid.mlir | 63 | ||||
-rw-r--r-- | mlir/test/Dialect/OpenMP/roundtrip.mlir | 21 |
4 files changed, 243 insertions, 1 deletions
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td index 5d84217..44f3e5b 100644 --- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td +++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td @@ -134,6 +134,97 @@ def DeclareTargetAttr : OpenMP_Attr<"DeclareTarget", "declaretarget"> { } //===----------------------------------------------------------------------===// +// 2.19.4 Data-Sharing Attribute Clauses +//===----------------------------------------------------------------------===// + +def DataSharingTypePrivate : I32EnumAttrCase<"Private", 0, "private">; +def DataSharingTypeFirstPrivate : I32EnumAttrCase<"FirstPrivate", 1, "firstprivate">; + +def DataSharingClauseType : I32EnumAttr< + "DataSharingClauseType", + "Type of a data-sharing clause", + [DataSharingTypePrivate, DataSharingTypeFirstPrivate]> { + let genSpecializedAttr = 0; + let cppNamespace = "::mlir::omp"; +} + +def DataSharingClauseTypeAttr : EnumAttr< + OpenMP_Dialect, DataSharingClauseType, "data_sharing_type"> { + let assemblyFormat = "`{` `type` `=` $value `}`"; +} + +def PrivateClauseOp : OpenMP_Op<"private", [IsolatedFromAbove]> { + let summary = "Provides declaration of [first]private logic."; + let description = [{ + This operation provides a declaration of how to implement the + [first]privatization of a variable. The dialect users should provide + information about how to create an instance of the type in the alloc region + and how to initialize the copy from the original item in the copy region. + + Examples: + --------- + * `private(x)` would be emitted as: + ```mlir + omp.private {type = private} @x.privatizer : !fir.ref<i32> alloc { + ^bb0(%arg0: !fir.ref<i32>): + %0 = ... allocate proper memory for the private clone ... + omp.yield(%0 : !fir.ref<i32>) + } + ``` + + * `firstprivate(x)` would be emitted as: + ```mlir + omp.private {type = firstprivate} @x.privatizer : !fir.ref<i32> alloc { + ^bb0(%arg0: !fir.ref<i32>): + %0 = ... allocate proper memory for the private clone ... + omp.yield(%0 : !fir.ref<i32>) + } copy { + ^bb0(%arg0: !fir.ref<i32>, %arg1: !fir.ref<i32>): + // %arg0 is the original host variable. Same as for `alloc`. + // %arg1 represents the memory allocated in `alloc`. + ... copy from host to the privatized clone .... + omp.yield(%arg1 : !fir.ref<i32>) + } + ``` + + There are no restrictions on the body except for: + - The `alloc` region has a single argument. + - The `copy` region has 2 arguments. + - Both regions are terminated by `omp.yield` ops. + The above restrictions and other obvious restrictions (e.g. verifying the + type of yielded values) are verified by the custom op verifier. The actual + contents of the blocks inside both regions are not verified. + + Instances of this op would then be used by ops that model directives that + accept data-sharing attribute clauses. + + The $sym_name attribute provides a symbol by which the privatizer op can be + referenced by other dialect ops. + + The $type attribute is the type of the value being privatized. + + The $data_sharing_type attribute specifies whether privatizer corresponds + to a `private` or a `firstprivate` clause. + }]; + + let arguments = (ins SymbolNameAttr:$sym_name, + TypeAttrOf<AnyType>:$type, + DataSharingClauseTypeAttr:$data_sharing_type); + + let regions = (region MinSizedRegion<1>:$alloc_region, + AnyRegion:$copy_region); + + let assemblyFormat = [{ + $data_sharing_type $sym_name `:` $type + `alloc` $alloc_region + (`copy` $copy_region^)? + attr-dict + }]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// // 2.6 parallel Construct //===----------------------------------------------------------------------===// @@ -609,7 +700,7 @@ def SimdLoopOp : OpenMP_Op<"simdloop", [AttrSizedOperandSegments, def YieldOp : OpenMP_Op<"yield", [Pure, ReturnLike, Terminator, ParentOneOf<["WsLoopOp", "ReductionDeclareOp", - "AtomicUpdateOp", "SimdLoopOp"]>]> { + "AtomicUpdateOp", "SimdLoopOp", "PrivateClauseOp"]>]> { let summary = "loop yield and termination operation"; let description = [{ "omp.yield" yields SSA values from the OpenMP dialect op region and diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp index 394f062..ef08bd8 100644 --- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp +++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp @@ -1662,6 +1662,73 @@ LogicalResult DataBoundsOp::verify() { return success(); } +LogicalResult PrivateClauseOp::verify() { + Type symType = getType(); + + auto verifyTerminator = [&](Operation *terminator) -> LogicalResult { + if (!terminator->hasSuccessors() && !llvm::isa<YieldOp>(terminator)) + return mlir::emitError(terminator->getLoc()) + << "expected exit block terminator to be an `omp.yield` op."; + + YieldOp yieldOp = llvm::cast<YieldOp>(terminator); + TypeRange yieldedTypes = yieldOp.getResults().getTypes(); + + if (yieldedTypes.size() == 1 && yieldedTypes.front() == symType) + return success(); + + auto error = mlir::emitError(yieldOp.getLoc()) + << "Invalid yielded value. Expected type: " << symType + << ", got: "; + + if (yieldedTypes.empty()) + error << "None"; + else + error << yieldedTypes; + + return error; + }; + + auto verifyRegion = [&](Region ®ion, unsigned expectedNumArgs, + StringRef regionName) -> LogicalResult { + assert(!region.empty()); + + if (region.getNumArguments() != expectedNumArgs) + return mlir::emitError(region.getLoc()) + << "`" << regionName << "`: " + << "expected " << expectedNumArgs + << " region arguments, got: " << region.getNumArguments(); + + for (Block &block : region) { + // MLIR will verify the absence of the terminator for us. + if (!block.mightHaveTerminator()) + continue; + + if (failed(verifyTerminator(block.getTerminator()))) + return failure(); + } + + return success(); + }; + + if (failed(verifyRegion(getAllocRegion(), /*expectedNumArgs=*/1, "alloc"))) + return failure(); + + DataSharingClauseType dsType = getDataSharingType(); + + if (dsType == DataSharingClauseType::Private && !getCopyRegion().empty()) + return emitError("`private` clauses require only an `alloc` region."); + + if (dsType == DataSharingClauseType::FirstPrivate && getCopyRegion().empty()) + return emitError( + "`firstprivate` clauses require both `alloc` and `copy` regions."); + + if (dsType == DataSharingClauseType::FirstPrivate && + failed(verifyRegion(getCopyRegion(), /*expectedNumArgs=*/2, "copy"))) + return failure(); + + return success(); +} + #define GET_ATTRDEF_CLASSES #include "mlir/Dialect/OpenMP/OpenMPOpsAttributes.cpp.inc" diff --git a/mlir/test/Dialect/OpenMP/invalid.mlir b/mlir/test/Dialect/OpenMP/invalid.mlir index 812b79e..59b4239 100644 --- a/mlir/test/Dialect/OpenMP/invalid.mlir +++ b/mlir/test/Dialect/OpenMP/invalid.mlir @@ -1738,3 +1738,66 @@ func.func @omp_distribute(%data_var : memref<i32>) -> () { "omp.terminator"() : () -> () }) : (memref<i32>) -> () } + +// ----- + +omp.private {type = private} @x.privatizer : i32 alloc { +^bb0(%arg0: i32): + %0 = arith.constant 0.0 : f32 + // expected-error @below {{Invalid yielded value. Expected type: 'i32', got: 'f32'}} + omp.yield(%0 : f32) +} + +// ----- + +omp.private {type = private} @x.privatizer : i32 alloc { +^bb0(%arg0: i32): + // expected-error @below {{Invalid yielded value. Expected type: 'i32', got: None}} + omp.yield +} + +// ----- + +omp.private {type = private} @x.privatizer : i32 alloc { +^bb0(%arg0: i32): + // expected-error @below {{expected exit block terminator to be an `omp.yield` op.}} + omp.terminator +} + +// ----- + +// expected-error @below {{`alloc`: expected 1 region arguments, got: 2}} +omp.private {type = private} @x.privatizer : f32 alloc { +^bb0(%arg0: f32, %arg1: f32): + omp.yield(%arg0 : f32) +} + +// ----- + +// expected-error @below {{`copy`: expected 2 region arguments, got: 1}} +omp.private {type = firstprivate} @x.privatizer : f32 alloc { +^bb0(%arg0: f32): + omp.yield(%arg0 : f32) +} copy { +^bb0(%arg0: f32): + omp.yield(%arg0 : f32) +} + +// ----- + +// expected-error @below {{`private` clauses require only an `alloc` region.}} +omp.private {type = private} @x.privatizer : f32 alloc { +^bb0(%arg0: f32): + omp.yield(%arg0 : f32) +} copy { +^bb0(%arg0: f32, %arg1 : f32): + omp.yield(%arg0 : f32) +} + +// ----- + +// expected-error @below {{`firstprivate` clauses require both `alloc` and `copy` regions.}} +omp.private {type = firstprivate} @x.privatizer : f32 alloc { +^bb0(%arg0: f32): + omp.yield(%arg0 : f32) +} diff --git a/mlir/test/Dialect/OpenMP/roundtrip.mlir b/mlir/test/Dialect/OpenMP/roundtrip.mlir new file mode 100644 index 0000000..2553442 --- /dev/null +++ b/mlir/test/Dialect/OpenMP/roundtrip.mlir @@ -0,0 +1,21 @@ +// RUN: mlir-opt -verify-diagnostics %s | mlir-opt | FileCheck %s + +// CHECK: omp.private {type = private} @x.privatizer : !llvm.ptr alloc { +omp.private {type = private} @x.privatizer : !llvm.ptr alloc { +// CHECK: ^bb0(%arg0: {{.*}}): +^bb0(%arg0: !llvm.ptr): + omp.yield(%arg0 : !llvm.ptr) +} + +// CHECK: omp.private {type = firstprivate} @y.privatizer : !llvm.ptr alloc { +omp.private {type = firstprivate} @y.privatizer : !llvm.ptr alloc { +// CHECK: ^bb0(%arg0: {{.*}}): +^bb0(%arg0: !llvm.ptr): + omp.yield(%arg0 : !llvm.ptr) +// CHECK: } copy { +} copy { +// CHECK: ^bb0(%arg0: {{.*}}, %arg1: {{.*}}): +^bb0(%arg0: !llvm.ptr, %arg1: !llvm.ptr): + omp.yield(%arg0 : !llvm.ptr) +} + |