[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