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 @@ -31,17 +31,17 @@ Op; // Reduction operation enumeration. -def OpenACC_ReductionOperatorAdd : I32EnumAttrCase<"redop_add", 0>; -def OpenACC_ReductionOperatorMul : I32EnumAttrCase<"redop_mul", 1>; -def OpenACC_ReductionOperatorMax : I32EnumAttrCase<"redop_max", 2>; -def OpenACC_ReductionOperatorMin : I32EnumAttrCase<"redop_min", 3>; -def OpenACC_ReductionOperatorAnd : I32EnumAttrCase<"redop_and", 4>; -def OpenACC_ReductionOperatorOr : I32EnumAttrCase<"redop_or", 5>; -def OpenACC_ReductionOperatorXor : I32EnumAttrCase<"redop_xor", 6>; -def OpenACC_ReductionOperatorLogEqv : I32EnumAttrCase<"redop_leqv", 7>; -def OpenACC_ReductionOperatorLogNeqv : I32EnumAttrCase<"redop_lneqv", 8>; -def OpenACC_ReductionOperatorLogAnd : I32EnumAttrCase<"redop_land", 9>; -def OpenACC_ReductionOperatorLogOr : I32EnumAttrCase<"redop_lor", 10>; +def OpenACC_ReductionOperatorAdd : I32EnumAttrCase<"AccAdd", 0, "add">; +def OpenACC_ReductionOperatorMul : I32EnumAttrCase<"AccMul", 1, "mul">; +def OpenACC_ReductionOperatorMax : I32EnumAttrCase<"AccMax", 2, "max">; +def OpenACC_ReductionOperatorMin : I32EnumAttrCase<"AccMin", 3, "min">; +def OpenACC_ReductionOperatorAnd : I32EnumAttrCase<"AccIand", 4, "iand">; +def OpenACC_ReductionOperatorOr : I32EnumAttrCase<"AccIor", 5, "ior">; +def OpenACC_ReductionOperatorXor : I32EnumAttrCase<"AccXor", 6, "xor">; +def OpenACC_ReductionOperatorLogEqv : I32EnumAttrCase<"AccEqv", 7, "eqv">; +def OpenACC_ReductionOperatorLogNeqv : I32EnumAttrCase<"AccNeqv", 8, "neqv">; +def OpenACC_ReductionOperatorLogAnd : I32EnumAttrCase<"AccLand", 9, "land">; +def OpenACC_ReductionOperatorLogOr : I32EnumAttrCase<"AccLor", 10, "lor">; def OpenACC_ReductionOperator : I32EnumAttr<"ReductionOperator", "built-in reduction operations supported by OpenACC", @@ -57,7 +57,9 @@ } def OpenACC_ReductionOperatorAttr : EnumAttr; + "reduction_operator"> { + let assemblyFormat = [{ ```<` $value `>` }]; +} // Type used in operation below. def IntOrIndex : AnyTypeOf<[AnyInteger, Index]>; @@ -522,6 +524,85 @@ 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_add_i64 : i64 reduction_operator 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_add_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 + `reduction_operator` $reductionOperator + `init` $initRegion `combiner` $combinerRegion + }]; + + let hasRegionVerifier = 1; +} + //===----------------------------------------------------------------------===// // 2.5.1 parallel Construct //===----------------------------------------------------------------------===// @@ -986,7 +1067,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,48 +338,41 @@ // 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()) { if (yieldOp.getOperands().size() != 1 || yieldOp.getOperands().getTypes()[0] != type) - return op->emitOpError() << "expects " << regionName - << " region to " - "yield a value of the privatization type"; + return op->emitOpError() << "expects " << regionName << " region to " + "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 +382,52 @@ //===----------------------------------------------------------------------===// 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(); } 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 reduction_operator init { +} combiner {} + +// ----- + +// expected-error@+1 {{expects init region with one argument of the reduction type}} +acc.reduction.recipe @reduction_i64 : i64 reduction_operator init { +^bb0(%0: i32): + %1 = arith.constant 0 : i64 + acc.yield %1 : i64 +} combiner {} + +// ----- + +// expected-error@+1 {{expects init region to yield a value of the reduction type}} +acc.reduction.recipe @reduction_i64 : i64 reduction_operator init { +^bb0(%0: i64): + %1 = arith.constant 0 : i32 + acc.yield %1 : i32 +} combiner {} + +// ----- + +// expected-error@+1 {{expects non-empty combiner region}} +acc.reduction.recipe @reduction_i64 : i64 reduction_operator init { +^bb0(%0: i64): + %1 = arith.constant 0 : i64 + acc.yield %1 : i64 +} combiner {} +// ----- + +// expected-error@+1 {{expects combiner region with two arguments of the reduction type}} +acc.reduction.recipe @reduction_i64 : i64 reduction_operator init { +^bb0(%0: i64): + %1 = arith.constant 0 : i64 + acc.yield %1 : i64 +} combiner { +^bb0(%0: i32): + acc.yield %0 : i32 +} + +// ----- + +// expected-error@+1 {{expects combiner region with two arguments of the reduction type}} +acc.reduction.recipe @reduction_i64 : i64 reduction_operator init { +^bb0(%0: i64): + %1 = arith.constant 0 : i64 + acc.yield %1 : i64 +} combiner { +^bb0(%0: i64): + acc.yield %0 : i64 +} + +// ----- + +// expected-error@+1 {{expects combiner region to yield a value of the reduction type}} +acc.reduction.recipe @reduction_i64 : i64 reduction_operator init { +^bb0(%0: i64): + %1 = arith.constant 0 : i64 + acc.yield %1 : i64 +} combiner { +^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 reduction_operator init { +^bb0(%0: i64): + %1 = arith.constant 0 : i64 + acc.yield %1 : i64 +} combiner { +^bb0(%0: i64, %1: i64): + %2 = arith.addi %0, %1 : i64 + acc.yield %2 : i64 +} + +// CHECK-LABEL: acc.reduction.recipe @reduction_add_i64 : i64 reduction_operator init { +// CHECK: ^bb0(%{{.*}}: i64): +// CHECK: %[[C0:.*]] = arith.constant 0 : i64 +// CHECK: acc.yield %[[C0]] : i64 +// CHECK: } combiner { +// CHECK: ^bb0(%[[ARG0:.*]]: i64, %[[ARG1:.*]]: i64): +// CHECK: %[[RES:.*]] = arith.addi %[[ARG0]], %[[ARG1]] : i64 +// CHECK: acc.yield %[[RES]] : i64 +// CHECK: }