From 8504134c2f1ccb85c46fa5ba336ddbefdaf0529c Mon Sep 17 00:00:00 2001 From: Razvan Lupusoru Date: Tue, 26 Aug 2025 12:42:07 -0700 Subject: [PATCH 1/2] [mlir][acc] Add destroy region to reduction recipes Reduction recipes capture how a private copy is created. In some languages, like C++ class variables with destructors - that private copy also must be properly destroyed. Thus update the reduction recipe to contain a `destroy` region similarly to the private recipes. --- .../mlir/Dialect/OpenACC/OpenACCOps.td | 13 +++- mlir/test/Dialect/OpenACC/ops.mlir | 64 +++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td index 0db11aa9af683..173429b1a0329 100644 --- a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td +++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td @@ -1302,7 +1302,7 @@ def OpenACC_ReductionRecipeOp let description = [{ Declares an OpenACC reduction recipe. The operation requires two - mandatory regions. + mandatory regions and one optional region. 1. The initializer region specifies how to initialize the local reduction value. The region has a first argument that contains the value of the @@ -1313,6 +1313,9 @@ def OpenACC_ReductionRecipeOp values of the reduction type into one. It has at least two arguments and it is expected to `acc.yield` the combined value. Extra arguments can be added to deal with dynamic arrays. + 3. The destroy region specifies how to destruct the value when it reaches + its end of life. It takes the reduction value as argument. It is + optional. Example: @@ -1329,6 +1332,10 @@ def OpenACC_ReductionRecipeOp // two values into one. %2 = arith.addi %0, %1 : i64 acc.yield %2 : i64 + } destroy { + ^bb0(%0: i64) + // destroy region contains a sequence of operations to destruct the + // created copy. } // The reduction symbol is then used in the corresponding operation. @@ -1362,12 +1369,14 @@ def OpenACC_ReductionRecipeOp OpenACC_ReductionOperatorAttr:$reductionOperator); let regions = (region AnyRegion:$initRegion, - AnyRegion:$combinerRegion); + AnyRegion:$combinerRegion, + AnyRegion:$destroyRegion); let assemblyFormat = [{ $sym_name `:` $type attr-dict-with-keyword `reduction_operator` $reductionOperator `init` $initRegion `combiner` $combinerRegion + (`destroy` $destroyRegion^)? }]; let hasRegionVerifier = 1; diff --git a/mlir/test/Dialect/OpenACC/ops.mlir b/mlir/test/Dialect/OpenACC/ops.mlir index 7bb6cf43e49a7..5a3bbaf4252db 100644 --- a/mlir/test/Dialect/OpenACC/ops.mlir +++ b/mlir/test/Dialect/OpenACC/ops.mlir @@ -1954,6 +1954,70 @@ acc.reduction.recipe @reduction_add_memref_i32 : memref reduction_operator // CHECK-LABEL: acc.reduction.recipe @reduction_add_memref_i32 // CHECK: memref.alloca +// ----- + +// Test reduction recipe with destroy region using dynamic memory allocation +acc.reduction.recipe @reduction_add_with_destroy : memref reduction_operator init { +^bb0(%arg0: memref): + %cst = arith.constant 0.000000e+00 : f32 + %c0 = arith.constant 0 : index + %size = memref.dim %arg0, %c0 : memref + %alloc = memref.alloc(%size) : memref + %c1 = arith.constant 1 : index + scf.for %i = %c0 to %size step %c1 { + memref.store %cst, %alloc[%i] : memref + } + acc.yield %alloc : memref +} combiner { +^bb0(%arg0: memref, %arg1: memref): + %c0 = arith.constant 0 : index + %c1 = arith.constant 1 : index + %size = memref.dim %arg0, %c0 : memref + scf.for %i = %c0 to %size step %c1 { + %val0 = memref.load %arg0[%i] : memref + %val1 = memref.load %arg1[%i] : memref + %sum = arith.addf %val0, %val1 : f32 + memref.store %sum, %arg0[%i] : memref + } + acc.yield %arg0 : memref +} destroy { +^bb0(%arg0: memref): + // destroy region to deallocate dynamically allocated memory + memref.dealloc %arg0 : memref + acc.yield +} + +// CHECK-LABEL: acc.reduction.recipe @reduction_add_with_destroy : memref reduction_operator init { +// CHECK: ^bb0(%[[ARG:.*]]: memref): +// CHECK: %[[CST:.*]] = arith.constant 0.000000e+00 : f32 +// CHECK: %[[C0:.*]] = arith.constant 0 : index +// CHECK: %[[SIZE:.*]] = memref.dim %[[ARG]], %[[C0]] : memref +// CHECK: %[[ALLOC:.*]] = memref.alloc(%[[SIZE]]) : memref +// CHECK: %[[C1:.*]] = arith.constant 1 : index +// CHECK: scf.for %[[I:.*]] = %[[C0]] to %[[SIZE]] step %[[C1]] { +// CHECK: memref.store %[[CST]], %[[ALLOC]][%[[I]]] : memref +// CHECK: } +// CHECK: acc.yield %[[ALLOC]] : memref +// CHECK: } combiner { +// CHECK: ^bb0(%[[ARG0:.*]]: memref, %[[ARG1:.*]]: memref): +// CHECK: %[[C0_1:.*]] = arith.constant 0 : index +// CHECK: %[[C1_1:.*]] = arith.constant 1 : index +// CHECK: %[[SIZE_1:.*]] = memref.dim %[[ARG0]], %[[C0_1]] : memref +// CHECK: scf.for %[[I_1:.*]] = %[[C0_1]] to %[[SIZE_1]] step %[[C1_1]] { +// CHECK: %{{.*}} = memref.load %[[ARG0]][%[[I_1]]] : memref +// CHECK: %{{.*}} = memref.load %[[ARG1]][%[[I_1]]] : memref +// CHECK: %[[SUM:.*]] = arith.addf %{{.*}}, %{{.*}} : f32 +// CHECK: memref.store %[[SUM]], %[[ARG0]][%[[I_1]]] : memref +// CHECK: } +// CHECK: acc.yield %[[ARG0]] : memref +// CHECK: } destroy { +// CHECK: ^bb0(%[[ARG_DESTROY:.*]]: memref): +// CHECK: memref.dealloc %[[ARG_DESTROY]] : memref +// CHECK: acc.yield +// CHECK: } + +// ----- + acc.private.recipe @privatization_memref_i32 : memref init { ^bb0(%arg0: memref): %alloca = memref.alloca() : memref From 9f0cfe6b77c349ad5e2a3b097098501d90d4f23d Mon Sep 17 00:00:00 2001 From: Razvan Lupusoru Date: Tue, 26 Aug 2025 12:53:13 -0700 Subject: [PATCH 2/2] Fix optional wording --- mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td index 173429b1a0329..cfe73d81953db 100644 --- a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td +++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td @@ -1313,9 +1313,8 @@ def OpenACC_ReductionRecipeOp values of the reduction type into one. It has at least two arguments and it is expected to `acc.yield` the combined value. Extra arguments can be added to deal with dynamic arrays. - 3. The destroy region specifies how to destruct the value when it reaches - its end of life. It takes the reduction value as argument. It is - optional. + 3. The optional destroy region specifies how to destruct the value when it + reaches its end of life. It takes the reduction value as argument. Example: