[flang-commits] [flang] [mlir] [OpenACC] add acc::FirstprivateSaveOp (PR #211011)
Scott Manley via flang-commits
flang-commits at lists.llvm.org
Tue Jul 21 07:41:39 PDT 2026
https://github.com/rscottmanley created https://github.com/llvm/llvm-project/pull/211011
The `acc.firstprivate_save` operation is intended to provide a temporary link from the compute region's private variable and the firstprivate map init op. This facilitates analysis in cases where the compiler may determine that a firstprivate should be converted to a copy and can be safely erased when that analysis is complete.
>From c2ae0042cab5115e802afcfb98f58ebaf37741c4 Mon Sep 17 00:00:00 2001
From: Scott Manley <scmanley at nvidia.com>
Date: Fri, 17 Jul 2026 12:35:40 -0700
Subject: [PATCH] [OpenACC] add acc::FirstprivateSaveOp
The `acc.firstprivate_save` operation is intended to provide a temporary
link from the compute region's private variable and the firstprivate map
init op. This facilitates analysis in cases where the compiler may
determine that a firstprivate should be converted to a copy and can be
safely erased when that analysis is complete.
---
...cc-recipe-materialization-firstprivate.fir | 34 ++++++++++++-------
.../mlir/Dialect/OpenACC/OpenACCCGOps.td | 22 ++++++++++++
.../Transforms/ACCRecipeMaterialization.cpp | 12 +++++++
...c-recipe-materialization-firstprivate.mlir | 20 ++++++-----
4 files changed, 67 insertions(+), 21 deletions(-)
diff --git a/flang/test/Transforms/OpenACC/acc-recipe-materialization-firstprivate.fir b/flang/test/Transforms/OpenACC/acc-recipe-materialization-firstprivate.fir
index a2f1dbc6e6565..998740f0e8237 100644
--- a/flang/test/Transforms/OpenACC/acc-recipe-materialization-firstprivate.fir
+++ b/flang/test/Transforms/OpenACC/acc-recipe-materialization-firstprivate.fir
@@ -39,18 +39,26 @@ module {
// and an alloca (as per the recipe) inside the region.
// Then ensure that all uses are of the private alloca.
// CHECK-LABEL: func.func @firstpriv
-// CHECK: acc.parallel
-// CHECK: %[[ALLOCA:.*]] = fir.alloca i32 {{.*}}acc.var_name = #acc.var_name<"t">
-// CHECK: %[[FIRSTPRIVLOAD:.*]] = fir.load %{{.*}} : !fir.ref<i32>
-// CHECK: fir.store %[[FIRSTPRIVLOAD]] to %[[ALLOCA]] : !fir.ref<i32>
-// CHECK: %[[ALLOCALOAD:.*]] = fir.load %[[ALLOCA]] : !fir.ref<i32>
-// CHECK: %[[ADDI:.*]] = arith.addi %[[ALLOCALOAD]], %c1{{.*}} : i32
-// CHECK: fir.store %[[ADDI]] to %[[ALLOCA]] : !fir.ref<i32>
+// CHECK: [[DECL:%.*]] = fir.declare
+// CHECK: [[MAP:%.*]] = acc.firstprivate_map varPtr([[DECL]] : !fir.ref<i32>) -> !fir.ref<i32> {implicit = true, name = "t"}
+// CHECK: acc.parallel {
+// CHECK: [[FIRSTPRIV:%.*]] = fir.alloca i32 {{.*}}acc.var_name = #acc.var_name<"t">
+// CHECK: [[FIRSTPRIVLOAD:%.*]] = fir.load [[MAP]] : !fir.ref<i32>
+// CHECK: fir.store [[FIRSTPRIVLOAD]] to [[FIRSTPRIV]] : !fir.ref<i32>
+// CHECK: [[FIRSTPRIVLOAD:%.*]] = fir.load [[FIRSTPRIV]] : !fir.ref<i32>
+// CHECK: [[ADDI:%.*]] = arith.addi [[FIRSTPRIVLOAD]], %c1{{.*}} : i32
+// CHECK: fir.store [[ADDI]] to [[FIRSTPRIV]] : !fir.ref<i32>
+// CHECK-NEXT: [[VALUELOAD:%.*]] = fir.load
+// CHECK-NEXT: acc.firstprivate_save [[VALUELOAD]] to [[MAP]] : i32 -> !fir.ref<i32>
+// With -acc-optimize-firstprivate-map, the map is retained because of the
+// firstprivate_save live-out.
// CHECK-MAP-LABEL: func.func @firstpriv
-// CHECK-MAP: fir.load {{.*}} : !fir.ref<i32>
-// CHECK-MAP: acc.parallel {
-// CHECK-MAP-NOT: acc.firstprivate_map
-// CHECK-MAP: fir.alloca i32 {{.*}}acc.var_name = #acc.var_name<"t">
-// CHECK-MAP: fir.store {{.*}} to {{.*}} : !fir.ref<i32>
-// CHECK-MAP: arith.addi {{.*}} %c1
+// CHECK-MAP: [[DECL:%.*]] = fir.declare
+// CHECK-MAP: [[MAP:%.*]] = acc.firstprivate_map varPtr([[DECL]] : !fir.ref<i32>) -> !fir.ref<i32> {implicit = true, name = "t"}
+// CHECK-MAP: acc.parallel {
+// CHECK-MAP: [[FIRSTPRIV:%.*]] = fir.alloca i32 {{.*}}acc.var_name = #acc.var_name<"t">
+// CHECK-MAP: fir.load [[MAP]] : !fir.ref<i32>
+// CHECK-MAP: fir.store {{.*}} to [[FIRSTPRIV]] : !fir.ref<i32>
+// CHECK-MAP: arith.addi {{.*}} %c1
+// CHECK-MAP: acc.firstprivate_save {{.*}} to [[MAP]] : i32 -> !fir.ref<i32>
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCCGOps.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCCGOps.td
index 730ea1f73a3ac..4b1895d4adabc 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCCGOps.td
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCCGOps.td
@@ -302,6 +302,28 @@ def OpenACC_FirstprivateMapInitialOp
let extraClassDeclaration = extraClassDeclarationBase;
}
+//===----------------------------------------------------------------------===//
+// acc.firstprivate_save
+//===----------------------------------------------------------------------===//
+
+def OpenACC_FirstprivateSaveOp : OpenACC_Op<"firstprivate_save", []> {
+ let summary = "\"Saves\" a value to a firstprivate variable";
+ let description = [{
+ The `acc.firstprivate_save` operation is intended to provide a temporary
+ link from the compute region's private variable and the firstprivate map
+ init op. This facilitates analysis in cases where the compiler may
+ determine that a firstprivate should be converted to a copy and can be
+ safely erased when that analysis is complete.
+ }];
+ let arguments = (ins AnyType:$value,
+ Arg<OpenACC_PointerLikeType,
+ "FirstprivateMapInitOp",
+ [MemWrite]>:$memref);
+ let assemblyFormat = [{
+ $value `to` $memref `:` type($value) `->` type($memref) attr-dict
+ }];
+}
+
//===----------------------------------------------------------------------===//
// acc.privatize
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCRecipeMaterialization.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCRecipeMaterialization.cpp
index 70aacd251c329..712a7b82a02bb 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCRecipeMaterialization.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCRecipeMaterialization.cpp
@@ -305,6 +305,7 @@ LogicalResult ACCRecipeMaterialization::materialize(
auto [results, ip] = acc::cloneACCRegionInto(
&initRegion, block, block->begin(), mapping, {accPtr});
assert(results.size() == 1 && "expected single result from init region");
+ Value alloca = results[0];
saveVarName(op.getAccVar(), results[0]);
resolveVarNamePlaceholders(block, ip, acc::getVariableName(op.getAccVar()));
// We want the copy to store the origPtr to private
@@ -318,6 +319,17 @@ LogicalResult ACCRecipeMaterialization::materialize(
Region ©Region = recipe.getCopyRegion();
setLocation(copyRegion, loc);
acc::cloneACCRegionInto(©Region, block, std::next(ip), mapping, {});
+
+ // Create a temporary acc.firstprivate_save op to keep the value live-out
+ // until after other passes have determined it is truly firstprivate.
+ b.setInsertionPoint(accOp.getBody().getTerminator());
+ auto ptrLikeType = dyn_cast<acc::PointerLikeType>(alloca.getType());
+ assert(ptrLikeType && "alloca must be a pointer-like type");
+ auto xTyped = cast<TypedValue<acc::PointerLikeType>>(alloca);
+ assert(xTyped && "alloca must be a typed value");
+ auto loadOp = ptrLikeType.genLoad(b, op.getLoc(), xTyped, {});
+ acc::FirstprivateSaveOp::create(b, op.getLoc(), loadOp, origPtr);
+
if (!recipe.getDestroyRegion().empty()) {
// origPtr was already pushed.
cloneDestroy(loc, recipe, block, std::prev(block->end()), results);
diff --git a/mlir/test/Dialect/OpenACC/acc-recipe-materialization-firstprivate.mlir b/mlir/test/Dialect/OpenACC/acc-recipe-materialization-firstprivate.mlir
index e100ec2df139b..533c7e6ec22dd 100644
--- a/mlir/test/Dialect/OpenACC/acc-recipe-materialization-firstprivate.mlir
+++ b/mlir/test/Dialect/OpenACC/acc-recipe-materialization-firstprivate.mlir
@@ -24,14 +24,18 @@ acc.private.recipe @privatization_memref_i32 : memref<i32> init {
// and an alloca (as per the recipe) inside the region.
// Then ensure that all uses are of the private alloca.
// CHECK-LABEL: func.func @firstpriv
-// CHECK: acc.parallel {
-// CHECK: %[[ALLOCA:.*]] = memref.alloca() {acc.var_name = #acc.var_name<"t">} : memref<i32>
-// CHECK: %[[FIRSTPRIVLOAD:.*]] = memref.load %{{.*}}[] : memref<i32>
-// CHECK: memref.store %[[FIRSTPRIVLOAD]], %[[ALLOCA]][] : memref<i32>
-// CHECK: %[[ALLOCALOAD:.*]] = memref.load %[[ALLOCA]][] : memref<i32>
-// CHECK: %[[ADDI:.*]] = arith.addi %[[ALLOCALOAD]], %c1{{.*}} : i32
-// CHECK: memref.store %[[ADDI]], %[[ALLOCA]][] : memref<i32>
-// CHECK: memref.dealloc %[[ALLOCA]] : memref<i32>
+// CHECK: [[ALLOCA:%.*]] = memref.alloca() : memref<i32>
+// CHECK: [[MAP:%.*]] = acc.firstprivate_map varPtr([[ALLOCA]] : memref<i32>) -> memref<i32> {implicit = true, name = "t"}
+// CHECK: acc.parallel {
+// CHECK: [[FIRSTPRIV:%.*]] = memref.alloca() {acc.var_name = #acc.var_name<"t">} : memref<i32>
+// CHECK: [[FIRSTPRIVLOAD:%.*]] = memref.load [[MAP]][] : memref<i32>
+// CHECK: memref.store [[FIRSTPRIVLOAD]], [[FIRSTPRIV]][] : memref<i32>
+// CHECK: [[FIRSTPRIVLOAD:%.*]] = memref.load [[FIRSTPRIV]][] : memref<i32>
+// CHECK: [[ADDI:%.*]] = arith.addi [[FIRSTPRIVLOAD]], %c1{{.*}} : i32
+// CHECK: memref.store [[ADDI]], [[FIRSTPRIV]][] : memref<i32>
+// CHECK-NEXT: [[VALUELOAD:%.*]] = memref.load
+// CHECK-NEXT: acc.firstprivate_save [[VALUELOAD]] to [[MAP]] : i32 -> memref<i32>
+// CHECK-NEXT: memref.dealloc [[FIRSTPRIV]] : memref<i32>
func.func @firstpriv() {
%c1336 = arith.constant 1336 : i32
More information about the flang-commits
mailing list