[Mlir-commits] [mlir] [acc] Add an API to make private recipe out of firstprivate recipes (PR #170588)
Susan Tan ス-ザン タン
llvmlistbot at llvm.org
Wed Dec 3 17:04:41 PST 2025
https://github.com/SusanTan updated https://github.com/llvm/llvm-project/pull/170588
>From 3b14ec10075859be801c1909cac6959254cc3b44 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Wed, 3 Dec 2025 16:52:52 -0800
Subject: [PATCH 1/3] first implementation
---
.../mlir/Dialect/OpenACC/OpenACCOps.td | 9 +++++
mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp | 33 +++++++++++++++++++
...pe-populate-private-from-firstprivate.mlir | 17 ++++++++++
.../Dialect/OpenACC/TestRecipePopulate.cpp | 23 +++++++++++++
4 files changed, 82 insertions(+)
create mode 100644 mlir/test/Dialect/OpenACC/recipe-populate-private-from-firstprivate.mlir
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
index fcfe959709f09..ada77479ce4a3 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
@@ -1394,6 +1394,15 @@ def OpenACC_PrivateRecipeOp
::mlir::Type varType,
::llvm::StringRef varName = "",
::mlir::ValueRange bounds = {});
+
+ /// Creates a PrivateRecipeOp using the same variable type as an existing
+ /// FirstprivateRecipeOp. This is a convenience wrapper that forwards to
+ /// the type-based createAndPopulate overload.
+ static std::optional<PrivateRecipeOp> createAndPopulate(
+ ::mlir::OpBuilder &builder,
+ ::mlir::Location loc,
+ ::llvm::StringRef recipeName,
+ ::mlir::acc::FirstprivateRecipeOp firstprivRecipe);
}];
}
diff --git a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
index 9235f89b7969a..9f4f587a387f4 100644
--- a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
+++ b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
@@ -15,6 +15,7 @@
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/DialectImplementation.h"
+#include "mlir/IR/IRMapping.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/OpImplementation.h"
#include "mlir/IR/SymbolTable.h"
@@ -1449,6 +1450,38 @@ PrivateRecipeOp::createAndPopulate(OpBuilder &builder, Location loc,
return recipe;
}
+std::optional<PrivateRecipeOp>
+PrivateRecipeOp::createAndPopulate(OpBuilder &builder, Location loc,
+ StringRef recipeName,
+ FirstprivateRecipeOp firstprivRecipe) {
+ // Create the private.recipe op with the same type as the firstprivate.recipe.
+ OpBuilder::InsertionGuard guard(builder);
+ auto varType = firstprivRecipe.getType();
+ auto recipe = PrivateRecipeOp::create(builder, loc, recipeName, varType);
+
+ // Clone the init region: same argument list (original + bounds...), same
+ // body, same yield value. This matches PrivateRecipeOp's expected init
+ // signature.
+ {
+ IRMapping mapping;
+ firstprivRecipe.getInitRegion().cloneInto(&recipe.getInitRegion(), mapping);
+ }
+
+ // Clone destroy region if the firstprivate.recipe has one. The destroy region
+ // signatures (original, privatized, bounds...) are compatible between the
+ // two.
+ if (!firstprivRecipe.getDestroyRegion().empty()) {
+ IRMapping mapping;
+ firstprivRecipe.getDestroyRegion().cloneInto(&recipe.getDestroyRegion(),
+ mapping);
+ }
+
+ // The copy region from firstprivate is intentionally ignored: private recipes
+ // only have init + optional destroy.
+
+ return recipe;
+}
+
//===----------------------------------------------------------------------===//
// FirstprivateRecipeOp
//===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/OpenACC/recipe-populate-private-from-firstprivate.mlir b/mlir/test/Dialect/OpenACC/recipe-populate-private-from-firstprivate.mlir
new file mode 100644
index 0000000000000..a315a7b4b5b3e
--- /dev/null
+++ b/mlir/test/Dialect/OpenACC/recipe-populate-private-from-firstprivate.mlir
@@ -0,0 +1,17 @@
+// RUN: mlir-opt %s --split-input-file --pass-pipeline="builtin.module(test-acc-recipe-populate{recipe-type=private_from_firstprivate})" | FileCheck %s
+
+// Verify that we can create a private recipe using the convenience overload
+// that takes an existing firstprivate recipe as input.
+// CHECK: acc.firstprivate.recipe @first_firstprivate_scalar : memref<f32> init
+// CHECK: acc.private.recipe @private_from_firstprivate_scalar : memref<f32> init {
+// CHECK: ^bb0(%{{.*}}: memref<f32>):
+// CHECK: %[[ALLOC:.*]] = memref.alloca() {acc.var_name = #acc.var_name<"scalar">} : memref<f32>
+// CHECK: acc.yield %[[ALLOC]] : memref<f32>
+// CHECK: }
+
+func.func @test_scalar() {
+ %0 = memref.alloca() {test.var = "scalar"} : memref<f32>
+ return
+}
+
+
diff --git a/mlir/test/lib/Dialect/OpenACC/TestRecipePopulate.cpp b/mlir/test/lib/Dialect/OpenACC/TestRecipePopulate.cpp
index 35f092c2188a6..2506ca4fc39ce 100644
--- a/mlir/test/lib/Dialect/OpenACC/TestRecipePopulate.cpp
+++ b/mlir/test/lib/Dialect/OpenACC/TestRecipePopulate.cpp
@@ -93,6 +93,29 @@ void TestRecipePopulatePass::runOnOperation() {
if (!recipe) {
op->emitError("Failed to create firstprivate recipe for ") << varName;
}
+ } else if (recipeType == "private_from_firstprivate") {
+ // First create a firstprivate recipe, then use it to drive creation of a
+ // matching private recipe via the convenience overload. Give each recipe
+ // a stable, predictable name so tests can check both.
+ std::string firstprivName = "first_firstprivate_" + varName;
+ std::string privName = "private_from_firstprivate_" + varName;
+
+ auto firstpriv = FirstprivateRecipeOp::createAndPopulate(
+ builder, loc, firstprivName, var.getType(), varName, bounds);
+
+ if (!firstpriv) {
+ op->emitError("Failed to create firstprivate recipe for ") << varName;
+ return;
+ }
+
+ auto priv = PrivateRecipeOp::createAndPopulate(builder, loc, privName,
+ *firstpriv);
+
+ if (!priv) {
+ op->emitError(
+ "Failed to create private recipe (from firstprivate) for ")
+ << varName;
+ }
}
}
}
>From 73b0eecca5ff70e47cb5e829a1569e36521133f8 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Wed, 3 Dec 2025 16:58:00 -0800
Subject: [PATCH 2/3] clean up comments
---
.../include/mlir/Dialect/OpenACC/OpenACCOps.td | 3 +--
mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp | 18 ++++--------------
2 files changed, 5 insertions(+), 16 deletions(-)
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
index ada77479ce4a3..02fd6670410f8 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
@@ -1396,8 +1396,7 @@ def OpenACC_PrivateRecipeOp
::mlir::ValueRange bounds = {});
/// Creates a PrivateRecipeOp using the same variable type as an existing
- /// FirstprivateRecipeOp. This is a convenience wrapper that forwards to
- /// the type-based createAndPopulate overload.
+ /// FirstprivateRecipeOp. This is a useful in cases where we promote private variables to firstprivate by analysis
static std::optional<PrivateRecipeOp> createAndPopulate(
::mlir::OpBuilder &builder,
::mlir::Location loc,
diff --git a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
index 9f4f587a387f4..e2b341320433b 100644
--- a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
+++ b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
@@ -1459,26 +1459,16 @@ PrivateRecipeOp::createAndPopulate(OpBuilder &builder, Location loc,
auto varType = firstprivRecipe.getType();
auto recipe = PrivateRecipeOp::create(builder, loc, recipeName, varType);
- // Clone the init region: same argument list (original + bounds...), same
- // body, same yield value. This matches PrivateRecipeOp's expected init
- // signature.
- {
- IRMapping mapping;
- firstprivRecipe.getInitRegion().cloneInto(&recipe.getInitRegion(), mapping);
- }
+ // Clone the init region
+ IRMapping mapping;
+ firstprivRecipe.getInitRegion().cloneInto(&recipe.getInitRegion(), mapping);
- // Clone destroy region if the firstprivate.recipe has one. The destroy region
- // signatures (original, privatized, bounds...) are compatible between the
- // two.
+ // Clone destroy region if the firstprivate.recipe has one.
if (!firstprivRecipe.getDestroyRegion().empty()) {
IRMapping mapping;
firstprivRecipe.getDestroyRegion().cloneInto(&recipe.getDestroyRegion(),
mapping);
}
-
- // The copy region from firstprivate is intentionally ignored: private recipes
- // only have init + optional destroy.
-
return recipe;
}
>From 50c2842db3e184efe9286d609221cafc27565758 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Wed, 3 Dec 2025 17:04:24 -0800
Subject: [PATCH 3/3] add another test
---
...pe-populate-private-from-firstprivate.mlir | 17 ---
.../OpenACC/recipe-populate-private.mlir | 124 ++++++++++++------
2 files changed, 81 insertions(+), 60 deletions(-)
delete mode 100644 mlir/test/Dialect/OpenACC/recipe-populate-private-from-firstprivate.mlir
diff --git a/mlir/test/Dialect/OpenACC/recipe-populate-private-from-firstprivate.mlir b/mlir/test/Dialect/OpenACC/recipe-populate-private-from-firstprivate.mlir
deleted file mode 100644
index a315a7b4b5b3e..0000000000000
--- a/mlir/test/Dialect/OpenACC/recipe-populate-private-from-firstprivate.mlir
+++ /dev/null
@@ -1,17 +0,0 @@
-// RUN: mlir-opt %s --split-input-file --pass-pipeline="builtin.module(test-acc-recipe-populate{recipe-type=private_from_firstprivate})" | FileCheck %s
-
-// Verify that we can create a private recipe using the convenience overload
-// that takes an existing firstprivate recipe as input.
-// CHECK: acc.firstprivate.recipe @first_firstprivate_scalar : memref<f32> init
-// CHECK: acc.private.recipe @private_from_firstprivate_scalar : memref<f32> init {
-// CHECK: ^bb0(%{{.*}}: memref<f32>):
-// CHECK: %[[ALLOC:.*]] = memref.alloca() {acc.var_name = #acc.var_name<"scalar">} : memref<f32>
-// CHECK: acc.yield %[[ALLOC]] : memref<f32>
-// CHECK: }
-
-func.func @test_scalar() {
- %0 = memref.alloca() {test.var = "scalar"} : memref<f32>
- return
-}
-
-
diff --git a/mlir/test/Dialect/OpenACC/recipe-populate-private.mlir b/mlir/test/Dialect/OpenACC/recipe-populate-private.mlir
index 3d5a91839da0d..a3d208cdfa790 100644
--- a/mlir/test/Dialect/OpenACC/recipe-populate-private.mlir
+++ b/mlir/test/Dialect/OpenACC/recipe-populate-private.mlir
@@ -1,11 +1,12 @@
-// RUN: mlir-opt %s --split-input-file --pass-pipeline="builtin.module(test-acc-recipe-populate{recipe-type=private})" | FileCheck %s
+// RUN: mlir-opt %s --split-input-file --pass-pipeline="builtin.module(test-acc-recipe-populate{recipe-type=private})" | FileCheck %s --check-prefix=CHECK-PRIVATE
+// RUN: mlir-opt %s --split-input-file --pass-pipeline="builtin.module(test-acc-recipe-populate{recipe-type=private_from_firstprivate})" | FileCheck %s --check-prefix=CHECK-PRIV-FROM-FIRST
-// CHECK: acc.private.recipe @private_scalar : memref<f32> init {
-// CHECK: ^bb0(%{{.*}}: memref<f32>):
-// CHECK: %[[ALLOC:.*]] = memref.alloca() {acc.var_name = #acc.var_name<"scalar">} : memref<f32>
-// CHECK: acc.yield %[[ALLOC]] : memref<f32>
-// CHECK: }
-// CHECK-NOT: destroy
+// CHECK-PRIVATE: acc.private.recipe @private_scalar : memref<f32> init {
+// CHECK-PRIVATE: ^bb0(%{{.*}}: memref<f32>):
+// CHECK-PRIVATE: %[[ALLOC:.*]] = memref.alloca() {acc.var_name = #acc.var_name<"scalar">} : memref<f32>
+// CHECK-PRIVATE: acc.yield %[[ALLOC]] : memref<f32>
+// CHECK-PRIVATE: }
+// CHECK-PRIVATE-NOT: destroy
func.func @test_scalar() {
%0 = memref.alloca() {test.var = "scalar"} : memref<f32>
@@ -14,12 +15,12 @@ func.func @test_scalar() {
// -----
-// CHECK: acc.private.recipe @private_static_2d : memref<10x20xf32> init {
-// CHECK: ^bb0(%{{.*}}: memref<10x20xf32>):
-// CHECK: %[[ALLOC:.*]] = memref.alloca() {acc.var_name = #acc.var_name<"static_2d">} : memref<10x20xf32>
-// CHECK: acc.yield %[[ALLOC]] : memref<10x20xf32>
-// CHECK: }
-// CHECK-NOT: destroy
+// CHECK-PRIVATE: acc.private.recipe @private_static_2d : memref<10x20xf32> init {
+// CHECK-PRIVATE: ^bb0(%{{.*}}: memref<10x20xf32>):
+// CHECK-PRIVATE: %[[ALLOC:.*]] = memref.alloca() {acc.var_name = #acc.var_name<"static_2d">} : memref<10x20xf32>
+// CHECK-PRIVATE: acc.yield %[[ALLOC]] : memref<10x20xf32>
+// CHECK-PRIVATE: }
+// CHECK-PRIVATE-NOT: destroy
func.func @test_static_2d() {
%0 = memref.alloca() {test.var = "static_2d"} : memref<10x20xf32>
@@ -28,19 +29,19 @@ func.func @test_static_2d() {
// -----
-// CHECK: acc.private.recipe @private_dynamic_2d : memref<?x?xf32> init {
-// CHECK: ^bb0(%[[ARG:.*]]: memref<?x?xf32>):
-// CHECK: %[[C0:.*]] = arith.constant 0 : index
-// CHECK: %[[DIM0:.*]] = memref.dim %[[ARG]], %[[C0]] : memref<?x?xf32>
-// CHECK: %[[C1:.*]] = arith.constant 1 : index
-// CHECK: %[[DIM1:.*]] = memref.dim %[[ARG]], %[[C1]] : memref<?x?xf32>
-// CHECK: %[[ALLOC:.*]] = memref.alloc(%[[DIM0]], %[[DIM1]]) {acc.var_name = #acc.var_name<"dynamic_2d">} : memref<?x?xf32>
-// CHECK: acc.yield %[[ALLOC]] : memref<?x?xf32>
-// CHECK: } destroy {
-// CHECK: ^bb0(%{{.*}}: memref<?x?xf32>, %[[VAL:.*]]: memref<?x?xf32>):
-// CHECK: memref.dealloc %[[VAL]] : memref<?x?xf32>
-// CHECK: acc.terminator
-// CHECK: }
+// CHECK-PRIVATE: acc.private.recipe @private_dynamic_2d : memref<?x?xf32> init {
+// CHECK-PRIVATE: ^bb0(%[[ARG:.*]]: memref<?x?xf32>):
+// CHECK-PRIVATE: %[[C0:.*]] = arith.constant 0 : index
+// CHECK-PRIVATE: %[[DIM0:.*]] = memref.dim %[[ARG]], %[[C0]] : memref<?x?xf32>
+// CHECK-PRIVATE: %[[C1:.*]] = arith.constant 1 : index
+// CHECK-PRIVATE: %[[DIM1:.*]] = memref.dim %[[ARG]], %[[C1]] : memref<?x?xf32>
+// CHECK-PRIVATE: %[[ALLOC:.*]] = memref.alloc(%[[DIM0]], %[[DIM1]]) {acc.var_name = #acc.var_name<"dynamic_2d">} : memref<?x?xf32>
+// CHECK-PRIVATE: acc.yield %[[ALLOC]] : memref<?x?xf32>
+// CHECK-PRIVATE: } destroy {
+// CHECK-PRIVATE: ^bb0(%{{.*}}: memref<?x?xf32>, %[[VAL:.*]]: memref<?x?xf32>):
+// CHECK-PRIVATE: memref.dealloc %[[VAL]] : memref<?x?xf32>
+// CHECK-PRIVATE: acc.terminator
+// CHECK-PRIVATE: }
func.func @test_dynamic_2d(%arg0: index, %arg1: index) {
%0 = memref.alloc(%arg0, %arg1) {test.var = "dynamic_2d"} : memref<?x?xf32>
@@ -49,17 +50,17 @@ func.func @test_dynamic_2d(%arg0: index, %arg1: index) {
// -----
-// CHECK: acc.private.recipe @private_mixed_dims : memref<10x?xf32> init {
-// CHECK: ^bb0(%[[ARG:.*]]: memref<10x?xf32>):
-// CHECK: %[[C1:.*]] = arith.constant 1 : index
-// CHECK: %[[DIM1:.*]] = memref.dim %[[ARG]], %[[C1]] : memref<10x?xf32>
-// CHECK: %[[ALLOC:.*]] = memref.alloc(%[[DIM1]]) {acc.var_name = #acc.var_name<"mixed_dims">} : memref<10x?xf32>
-// CHECK: acc.yield %[[ALLOC]] : memref<10x?xf32>
-// CHECK: } destroy {
-// CHECK: ^bb0(%{{.*}}: memref<10x?xf32>, %[[VAL:.*]]: memref<10x?xf32>):
-// CHECK: memref.dealloc %[[VAL]] : memref<10x?xf32>
-// CHECK: acc.terminator
-// CHECK: }
+// CHECK-PRIVATE: acc.private.recipe @private_mixed_dims : memref<10x?xf32> init {
+// CHECK-PRIVATE: ^bb0(%[[ARG:.*]]: memref<10x?xf32>):
+// CHECK-PRIVATE: %[[C1:.*]] = arith.constant 1 : index
+// CHECK-PRIVATE: %[[DIM1:.*]] = memref.dim %[[ARG]], %[[C1]] : memref<10x?xf32>
+// CHECK-PRIVATE: %[[ALLOC:.*]] = memref.alloc(%[[DIM1]]) {acc.var_name = #acc.var_name<"mixed_dims">} : memref<10x?xf32>
+// CHECK-PRIVATE: acc.yield %[[ALLOC]] : memref<10x?xf32>
+// CHECK-PRIVATE: } destroy {
+// CHECK-PRIVATE: ^bb0(%{{.*}}: memref<10x?xf32>, %[[VAL:.*]]: memref<10x?xf32>):
+// CHECK-PRIVATE: memref.dealloc %[[VAL]] : memref<10x?xf32>
+// CHECK-PRIVATE: acc.terminator
+// CHECK-PRIVATE: }
func.func @test_mixed_dims(%arg0: index) {
%0 = memref.alloc(%arg0) {test.var = "mixed_dims"} : memref<10x?xf32>
@@ -68,15 +69,52 @@ func.func @test_mixed_dims(%arg0: index) {
// -----
-// CHECK: acc.private.recipe @private_scalar_int : memref<i32> init {
-// CHECK: ^bb0(%{{.*}}: memref<i32>):
-// CHECK: %[[ALLOC:.*]] = memref.alloca() {acc.var_name = #acc.var_name<"scalar_int">} : memref<i32>
-// CHECK: acc.yield %[[ALLOC]] : memref<i32>
-// CHECK: }
-// CHECK-NOT: destroy
+// CHECK-PRIVATE: acc.private.recipe @private_scalar_int : memref<i32> init {
+// CHECK-PRIVATE: ^bb0(%{{.*}}: memref<i32>):
+// CHECK-PRIVATE: %[[ALLOC:.*]] = memref.alloca() {acc.var_name = #acc.var_name<"scalar_int">} : memref<i32>
+// CHECK-PRIVATE: acc.yield %[[ALLOC]] : memref<i32>
+// CHECK-PRIVATE: }
+// CHECK-PRIVATE-NOT: destroy
func.func @test_scalar_int() {
%0 = memref.alloca() {test.var = "scalar_int"} : memref<i32>
return
}
+// -----
+
+// Verify that we can create a private recipe using the convenience overload
+// that takes an existing firstprivate recipe as input.
+// CHECK-PRIV-FROM-FIRST: acc.firstprivate.recipe @first_firstprivate_scalar : memref<f32> init
+// CHECK-PRIV-FROM-FIRST: acc.private.recipe @private_from_firstprivate_scalar : memref<f32> init {
+// CHECK-PRIV-FROM-FIRST: ^bb0(%{{.*}}: memref<f32>):
+// CHECK-PRIV-FROM-FIRST: %[[ALLOC:.*]] = memref.alloca() {acc.var_name = #acc.var_name<"scalar">} : memref<f32>
+// CHECK-PRIV-FROM-FIRST: acc.yield %[[ALLOC]] : memref<f32>
+// CHECK-PRIV-FROM-FIRST: }
+
+func.func @test_scalar_from_firstprivate() {
+ %0 = memref.alloca() {test.var = "scalar"} : memref<f32>
+ return
+}
+
+// -----
+
+// Verify that destroy regions are also cloned when creating a private recipe
+// from a firstprivate recipe that requires deallocation.
+// CHECK-PRIV-FROM-FIRST: acc.firstprivate.recipe @first_firstprivate_dynamic_d2 : memref<?x?xf32> init {
+// CHECK-PRIV-FROM-FIRST: } copy {
+// CHECK-PRIV-FROM-FIRST: } destroy {
+// CHECK-PRIV-FROM-FIRST: memref.dealloc
+// CHECK-PRIV-FROM-FIRST: acc.terminator
+// CHECK-PRIV-FROM-FIRST: }
+// CHECK-PRIV-FROM-FIRST: acc.private.recipe @private_from_firstprivate_dynamic_d2 : memref<?x?xf32> init {
+// CHECK-PRIV-FROM-FIRST: } destroy {
+// CHECK-PRIV-FROM-FIRST: memref.dealloc
+// CHECK-PRIV-FROM-FIRST: acc.terminator
+// CHECK-PRIV-FROM-FIRST: }
+
+func.func @test_dynamic_from_firstprivate(%arg0: index, %arg1: index) {
+ %0 = memref.alloc(%arg0, %arg1) {test.var = "dynamic_d2"} : memref<?x?xf32>
+ return
+}
+
More information about the Mlir-commits
mailing list