diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td --- a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td +++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td @@ -522,6 +522,113 @@ let hasRegionVerifier = 1; } +//===----------------------------------------------------------------------===// +// 2.5.15 reduction clause +//===----------------------------------------------------------------------===// + +def OpenACC_ReductionRecipeOp : OpenACC_Op<"reduction.recipe", + [IsolatedFromAbove, Symbol]> { + let summary = "reduction recipe"; + + let description = [{ + Declares an OpenACC reduction recipe. The operation requires two + mandatory regions. + + 1. The initializer region specifies how to initialize the local reduction + value. The region has an argument that contains the value of the + reduction accumulator at the start of the reduction. It is expected to + `acc.yield` the new value. + 2. The reduction region contains a sequences of operations to combine two + values of the reduction type into one. It has two arguments and it is + expected to `acc.yield` the combined value. + + Example: + + ```mlir + acc.reduction.recipe @reduction_i64 : i64 init { + ^bb0(%0: i64): + // init region contains a sequence of operations to initialize the local + // reduction value as specified in 2.5.15 + %c0 = arith.constant 0 : i64 + acc.yield %c0 : i64 + } combiner { + ^bb0(%0: i64, %1: i64) + // combiner region contains a sequence of operations to combine + // two values into one. + %2 = arith.addi %0, %1 : i64 + acc.yield %2 : i64 + } + + // The reduction symbol is then used in the corresponding operation. + acc.parallel reduction(@reduction_i64 -> %a : i64) { + } + ``` + + The following table lists the valid operators and the initialization values + according to OpenACC 3.3: + + |------------------------------------------------| + | C/C++ | Fortran | + |-----------------------|------------------------| + | operator | init value | operator | init value | + | + | 0 | + | 0 | + | * | 1 | * | 1 | + | max | least | max | least | + | min | largest | min | largest | + | & | ~0 | iand | all bits on | + | | | 0 | ior | 0 | + | ^ | 0 | ieor | 0 | + | && | 1 | .and. | .true. | + | || | 0 | .or. | .false. | + | | | .eqv. | .true. | + | | | .neqv. | .false. | + -------------------------------------------------| + }]; + + let arguments = (ins SymbolNameAttr:$sym_name, + TypeAttr:$type, + OpenACC_ReductionOperatorAttr:$reductionOperator); + + let regions = (region AnyRegion:$initRegion, + AnyRegion:$combinerRegion); + + let assemblyFormat = [{ + $sym_name `:` $type attr-dict-with-keyword `init` $initRegion + `combiner` $combinerRegion + }]; + + let hasRegionVerifier = 1; +} + +def ReductionOp : OpenACC_Op<"reduction"> { + let summary = "reduction op"; + + let description = [{ + Indicates the value that is produced by the current reduction-participating + entity for a reduction requested in some ancestor. + The reduction is identified by the accumulator, but the value of the + accumulator may not be updated immediately. + + Example: + ``` + acc.parallel reduction(@reduction_i64 -> %a : !llvm.ptr) { + %1 = arith.constant 10 : i64 + acc.reduction %1, %a : i64, !llvm.ptr + acc.yield + } + ``` + }]; + + let arguments= (ins AnyType:$operand, OpenACC_PointerLikeType:$accumulator); + + let assemblyFormat = [{ + $operand `,` $accumulator attr-dict `:` + type($operand) `,` type($accumulator) + }]; + + let hasVerifier = 1; +} + //===----------------------------------------------------------------------===// // 2.5.1 parallel Construct //===----------------------------------------------------------------------===// @@ -986,7 +1093,7 @@ // Yield operation for the acc.loop and acc.parallel operations. def OpenACC_YieldOp : OpenACC_Op<"yield", [ReturnLike, Terminator, - ParentOneOf<["ParallelOp, LoopOp, SerialOp, PrivateRecipeOp, FirstprivateRecipeOp"]>]> { + ParentOneOf<["FirstprivateRecipeOp, LoopOp, ParallelOp, PrivateRecipeOp, ReductionRecipeOp, SerialOp"]>]> { let summary = "Acc yield and termination operation"; let description = [{ diff --git a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp --- a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp +++ b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp @@ -338,27 +338,21 @@ // PrivateRecipeOp //===----------------------------------------------------------------------===// -static LogicalResult verifyPrivateLikeRegion(Operation *op, Region ®ion, - StringRef regionName, Type type, - unsigned expectNbArg, - bool optionalRegion, - bool verifyYield) { - if (optionalRegion && region.empty()) +static LogicalResult verifyInitLikeSingleArgRegion( + Operation *op, Region ®ion, StringRef regionType, StringRef regionName, + Type type, bool verifyYield, bool optional = false) { + if (optional && region.empty()) return success(); if (region.empty()) return op->emitOpError() << "expects non-empty " << regionName << " region"; Block &firstBlock = region.front(); - if (expectNbArg == 1 && (firstBlock.getNumArguments() != 1 || - firstBlock.getArgument(0).getType() != type)) + if (firstBlock.getNumArguments() != 1 || + firstBlock.getArgument(0).getType() != type) return op->emitOpError() << "expects " << regionName << " region with one " - "argument of the privatization type"; - if (expectNbArg == 2 && (firstBlock.getNumArguments() != 2 || - firstBlock.getArgument(0).getType() != type)) - return op->emitOpError() << "expects " << regionName - << " region with two " - "arguments of the privatization type"; + "argument of the " + << regionType << " type"; if (verifyYield) { for (YieldOp yieldOp : region.getOps()) { @@ -366,20 +360,21 @@ yieldOp.getOperands().getTypes()[0] != type) return op->emitOpError() << "expects " << regionName << " region to " - "yield a value of the privatization type"; + "yield a value of the " + << regionType << " type"; } } return success(); } LogicalResult acc::PrivateRecipeOp::verifyRegions() { - if (failed(verifyPrivateLikeRegion(*this, getInitRegion(), "init", getType(), - 1, /*optional=*/false, - /*verifyYield=*/true))) + if (failed(verifyInitLikeSingleArgRegion(*this, getInitRegion(), + "privatization", "init", getType(), + /*verifyYield=*/true))) return failure(); - if (failed(verifyPrivateLikeRegion(*this, getDestroyRegion(), "destroy", - getType(), 1, /*optional=*/true, - /*verifyYield=*/false))) + if (failed(verifyInitLikeSingleArgRegion( + *this, getDestroyRegion(), "privatization", "destroy", getType(), + /*verifyYield=*/false, /*optional=*/true))) return failure(); return success(); } @@ -389,19 +384,61 @@ //===----------------------------------------------------------------------===// LogicalResult acc::FirstprivateRecipeOp::verifyRegions() { - if (failed(verifyPrivateLikeRegion(*this, getInitRegion(), "init", getType(), - 1, /*optional=*/false, - /*verifyYield=*/true))) + if (failed(verifyInitLikeSingleArgRegion(*this, getInitRegion(), + "privatization", "init", getType(), + /*verifyYield=*/true))) return failure(); - if (failed(verifyPrivateLikeRegion(*this, getCopyRegion(), "copy", getType(), - 2, /*optional=*/false, - /*verifyYield=*/false))) + if (getCopyRegion().empty()) + return emitOpError() << "expects non-empty copy region"; + + Block &firstBlock = getCopyRegion().front(); + if (firstBlock.getNumArguments() != 2 || + firstBlock.getArgument(0).getType() != getType()) + return emitOpError() << "expects copy region with two arguments of the " + "privatization type"; + + if (failed(verifyInitLikeSingleArgRegion(*this, getDestroyRegion(), + "privatization", "destroy", + getType(), /*verifyYield=*/false))) return failure(); - if (failed(verifyPrivateLikeRegion(*this, getDestroyRegion(), "destroy", - getType(), 1, /*optional=*/true, - /*verifyYield=*/false))) + + return success(); +} + +//===----------------------------------------------------------------------===// +// ReductionRecipeOp +//===----------------------------------------------------------------------===// + +LogicalResult acc::ReductionRecipeOp::verifyRegions() { + if (failed(verifyInitLikeSingleArgRegion(*this, getInitRegion(), "reduction", + "init", getType(), + /*verifyYield=*/true))) return failure(); + + if (getCombinerRegion().empty()) + return emitOpError() << "expects non-empty combiner region"; + + Block &reductionBlock = getCombinerRegion().front(); + if (reductionBlock.getNumArguments() != 2 || + reductionBlock.getArgument(0).getType() != getType() || + reductionBlock.getArgument(1).getType() != getType()) + return emitOpError() << "expects combiner region with two arguments of " + << "the reduction type"; + + for (YieldOp yieldOp : getCombinerRegion().getOps()) { + if (yieldOp.getOperands().size() != 1 || + yieldOp.getOperands().getTypes()[0] != getType()) + return emitOpError() << "expects combiner region to yield a value " + "of the reduction type"; + } + + return success(); +} + +LogicalResult acc::ReductionOp::verify() { + // TODO when actual (acc.parallel, acc.serial, acc.loop) operation support the + // new reduction design. return success(); } diff --git a/mlir/test/Dialect/OpenACC/invalid.mlir b/mlir/test/Dialect/OpenACC/invalid.mlir --- a/mlir/test/Dialect/OpenACC/invalid.mlir +++ b/mlir/test/Dialect/OpenACC/invalid.mlir @@ -393,4 +393,72 @@ acc.yield } +// ----- + +// expected-error@+1 {{expects non-empty init region}} +acc.reduction.recipe @reduction_i64 : i64 init { +} reduction {} + +// ----- + +// expected-error@+1 {{expects init region with one argument of the reduction type}} +acc.reduction.recipe @reduction_i64 : i64 init { +^bb0(%0: i32): + %1 = arith.constant 0 : i64 + acc.yield %1 : i64 +} reduction {} + +// ----- + +// expected-error@+1 {{expects init region to yield a value of the reduction type}} +acc.reduction.recipe @reduction_i64 : i64 init { +^bb0(%0: i64): + %1 = arith.constant 0 : i32 + acc.yield %1 : i32 +} reduction {} + +// ----- + +// expected-error@+1 {{expects non-empty reduction region}} +acc.reduction.recipe @reduction_i64 : i64 init { +^bb0(%0: i64): + %1 = arith.constant 0 : i64 + acc.yield %1 : i64 +} reduction {} +// ----- + +// expected-error@+1 {{expects reduction region with two arguments of the reduction type}} +acc.reduction.recipe @reduction_i64 : i64 init { +^bb0(%0: i64): + %1 = arith.constant 0 : i64 + acc.yield %1 : i64 +} reduction { +^bb0(%0: i32): + acc.yield %0 : i32 +} + +// ----- + +// expected-error@+1 {{expects reduction region with two arguments of the reduction type}} +acc.reduction.recipe @reduction_i64 : i64 init { +^bb0(%0: i64): + %1 = arith.constant 0 : i64 + acc.yield %1 : i64 +} reduction { +^bb0(%0: i64): + acc.yield %0 : i64 +} + +// ----- + +// expected-error@+1 {{expects reduction region to yield a value of the reduction type}} +acc.reduction.recipe @reduction_i64 : i64 init { +^bb0(%0: i64): + %1 = arith.constant 0 : i64 + acc.yield %1 : i64 +} reduction { +^bb0(%0: i64, %1: i64): + %2 = arith.constant 0 : i32 + acc.yield %2 : i32 +} diff --git a/mlir/test/Dialect/OpenACC/ops.mlir b/mlir/test/Dialect/OpenACC/ops.mlir --- a/mlir/test/Dialect/OpenACC/ops.mlir +++ b/mlir/test/Dialect/OpenACC/ops.mlir @@ -1335,3 +1335,25 @@ // CHECK: ^bb0(%[[ARG0:.*]]: !llvm.struct<(i32, i32)>): // CHECK: func.call @destroy_struct(%[[ARG0]]) : (!llvm.struct<(i32, i32)>) -> () // CHECK: acc.terminator + +// ----- + +acc.reduction.recipe @reduction_add_i64 : i64 init { +^bb0(%0: i64): + %1 = arith.constant 0 : i64 + acc.yield %1 : i64 +} reduction { +^bb0(%0: i64, %1: i64): + %2 = arith.addi %0, %1 : i64 + acc.yield %2 : i64 +} + +// CHECK-LABEL: acc.reduction.recipe @reduction_add_i64 : i64 init { +// CHECK: ^bb0(%{{.*}}: i64): +// CHECK: %[[C0:.*]] = arith.constant 0 : i64 +// CHECK: acc.yield %[[C0]] : i64 +// CHECK: } reduction { +// CHECK: ^bb0(%[[ARG0:.*]]: i64, %[[ARG1:.*]]: i64): +// CHECK: %[[RES:.*]] = arith.addi %[[ARG0]], %[[ARG1]] : i64 +// CHECK: acc.yield %[[RES]] : i64 +// CHECK: }