[Mlir-commits] [mlir] [mlir][openacc] Report privates using thread-private storage (PR #224398)

Razvan Lupusoru llvmlistbot at llvm.org
Mon Sep 21 08:56:01 PDT 2026


https://github.com/razvanlupusoru updated https://github.com/llvm/llvm-project/pull/224398

>From cf5c87828768ed15c6bfc404d47870ec15250b72 Mon Sep 17 00:00:00 2001
From: Yian Su <yians at nvidia.com>
Date: Thu, 17 Sep 2026 12:38:02 -0700
Subject: [PATCH 1/2] [mlir][openacc] Report private arrays placed in local
 memory

ACCCGToGPU reports private arrays it places in GPU shared memory, but
says nothing when a private array is given per-thread stack storage.
Collect those names too and emit a matching remark, so -Minfo output
accounts for every private array placement.

Rank-0 privatizations are skipped: a private scalar is a register
candidate rather than local memory.
---
 .../Dialect/OpenACC/Transforms/ACCCGToGPU.cpp | 35 +++++++++++
 ...cg-to-gpu-private-local-memory-remark.mlir | 58 +++++++++++++++++++
 2 files changed, 93 insertions(+)
 create mode 100644 mlir/test/Dialect/OpenACC/acc-cg-to-gpu-private-local-memory-remark.mlir

diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCCGToGPU.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCCGToGPU.cpp
index 75dcafeadf9cb..59c5b68199d20 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCCGToGPU.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCCGToGPU.cpp
@@ -529,6 +529,12 @@ class ACCCGToGPULowering {
   /// Reserve \p bytes from the shared-memory budget.
   bool tryAllocateSharedMemory(int64_t bytes);
 
+  /// Record the array behind \p privateLocal as per-thread local memory for
+  /// the closing remark. Rank-0 privatizations are skipped: a scalar is a
+  /// register candidate, not local memory.
+  void recordLocalMemPrivate(acc::PrivateLocalOp privateLocal,
+                             MemRefType baseTy);
+
   /// Element size in bytes for \p elementType .
   int64_t getElementSizeInBytes(Location loc, Type elementType) const;
 
@@ -701,6 +707,7 @@ class ACCCGToGPULowering {
   acc::DefaultACCToGPUMappingPolicy defaultPolicy;
   SharedMemoryBudget sharedMemBudget;
   SmallVector<std::string> sharedMemPrivateVarNames;
+  SmallVector<std::string> localMemPrivateVarNames;
   llvm::SmallVector<Operation *, 4> deferredBarrierSeqLoops;
 
   Value getThreadId(Location loc, gpu::Dimension dim) {
@@ -865,6 +872,20 @@ bool ACCCGToGPULowering::tryAllocateSharedMemory(int64_t bytes) {
   return sharedMemBudget.tryAllocate(bytes);
 }
 
+void ACCCGToGPULowering::recordLocalMemPrivate(acc::PrivateLocalOp privateLocal,
+                                               MemRefType baseTy) {
+  if (baseTy.getRank() == 0)
+    return;
+  std::string varName =
+      privateLocal ? accSupport.getVariableName(privateLocal.getResult())
+                   : std::string();
+  if (varName.empty())
+    varName = "(*)";
+  // The same variable reaches this point once per acc.private_local it has.
+  if (!llvm::is_contained(localMemPrivateVarNames, varName))
+    localMemPrivateVarNames.push_back(varName);
+}
+
 FailureOr<arith::AtomicRMWKind>
 ACCCGToGPULowering::getReductionKind(acc::ReductionOperator redOp, Type type,
                                      Location loc) {
@@ -1250,6 +1271,14 @@ LogicalResult ACCCGToGPULowering::rewrite() {
   if (hasFailed)
     return failure();
 
+  if (!localMemPrivateVarNames.empty()) {
+    accSupport.emitRemark(computeRegion, [&]() {
+      return (llvm::Twine("Local memory used for ") +
+              llvm::join(localMemPrivateVarNames, ","))
+          .str();
+    });
+  }
+
   if (!sharedMemPrivateVarNames.empty()) {
     accSupport.emitRemark(computeRegion, [&]() {
       return (llvm::Twine("GPU shared memory used for ") +
@@ -2577,6 +2606,8 @@ Value ACCCGToGPULowering::processPrivatize(acc::PrivatizeOp privatize) {
   }
   Operation *privatizeUser = getOnlyUser(tracked);
   assert(privatizeUser && "expected PrivateLocalOp user for privatize");
+  acc::PrivateLocalOp privateLocalUser =
+      dyn_cast<acc::PrivateLocalOp>(privatizeUser);
 
   std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
             SmallVector<mlir::acc::GPUParallelDimAttr>>
@@ -2608,6 +2639,7 @@ Value ACCCGToGPULowering::processPrivatize(acc::PrivatizeOp privatize) {
   if (threadXActive &&
       canUseStackAlloca(baseTy, loc, options.maxThreadPrivateStack)) {
     auto alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
+    recordLocalMemPrivate(privateLocalUser, baseTy);
     mapping.map(privatize.getResult(), alloca.getResult());
     return alloca.getResult();
   }
@@ -2666,6 +2698,7 @@ Value ACCCGToGPULowering::processPrivatize(acc::PrivatizeOp privatize) {
       // Static sizes: use alloca (stack allocation)
       auto alloca =
           memref::AllocaOp::create(rewriter, privatize->getLoc(), baseTy);
+      recordLocalMemPrivate(privateLocalUser, baseTy);
       mapping.map(privatize.getResult(), alloca.getResult());
       return alloca.getResult();
     }
@@ -2852,6 +2885,7 @@ void ACCCGToGPULowering::processPrivateLocal(
          (arrayAccum && storageHasThreadX(privateLocal, computeRegion))) &&
         canUseStackAlloca(baseTy, loc, options.maxThreadPrivateStack)) {
       Value alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
+      recordLocalMemPrivate(privateLocal, baseTy);
       if (arrayAccum) {
         FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
             arrayAccum.getReductionOperator(), baseTy.getElementType(), loc);
@@ -2942,6 +2976,7 @@ void ACCCGToGPULowering::processPrivateLocal(
          (arrayAccum && storageHasThreadX(privateLocal, computeRegion))) &&
         canUseStackAlloca(baseTy, loc, options.maxThreadPrivateStack)) {
       Value alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
+      recordLocalMemPrivate(privateLocal, baseTy);
       if (arrayAccum) {
         FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
             arrayAccum.getReductionOperator(), baseTy.getElementType(), loc);
diff --git a/mlir/test/Dialect/OpenACC/acc-cg-to-gpu-private-local-memory-remark.mlir b/mlir/test/Dialect/OpenACC/acc-cg-to-gpu-private-local-memory-remark.mlir
new file mode 100644
index 0000000000000..b69cf304d7fec
--- /dev/null
+++ b/mlir/test/Dialect/OpenACC/acc-cg-to-gpu-private-local-memory-remark.mlir
@@ -0,0 +1,58 @@
+// RUN: mlir-opt %s --pass-pipeline="builtin.module(func.func(acc-cg-to-gpu))" \
+// RUN:   --remarks-filter="(open)?acc.*" 2>&1 | FileCheck %s
+
+// CHECK: Function=one_array | Remark="Local memory used for buf"
+func.func @one_array() {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : i32
+  %priv = acc.privatize par_dims(#acc<par_dims[thread_x]>)
+      : () -> !acc.private_type<memref<4xi32>>
+
+  acc.compute_region ins(%priv_in = %priv) : (!acc.private_type<memref<4xi32>>) {
+    %c0_k = arith.constant 0 : index
+    %c1_k = arith.constant 1 : i32
+    %local = acc.private_local %priv_in {acc.var_name = #acc.var_name<"buf">}
+        : (!acc.private_type<memref<4xi32>>) -> memref<4xi32>
+    memref.store %c1_k, %local[%c0_k] : memref<4xi32>
+    acc.yield
+  } <{origin = "acc.parallel"}>
+  return
+}
+
+// CHECK: Function=two_arrays | Remark="Local memory used for a,b"
+func.func @two_arrays() {
+  %priv_a = acc.privatize par_dims(#acc<par_dims[thread_x]>)
+      : () -> !acc.private_type<memref<4xi32>>
+  %priv_b = acc.privatize par_dims(#acc<par_dims[thread_x]>)
+      : () -> !acc.private_type<memref<8xi32>>
+
+  acc.compute_region ins(%a_in = %priv_a, %b_in = %priv_b)
+      : (!acc.private_type<memref<4xi32>>, !acc.private_type<memref<8xi32>>) {
+    %c0_k = arith.constant 0 : index
+    %c1_k = arith.constant 1 : i32
+    %a = acc.private_local %a_in {acc.var_name = #acc.var_name<"a">}
+        : (!acc.private_type<memref<4xi32>>) -> memref<4xi32>
+    %b = acc.private_local %b_in {acc.var_name = #acc.var_name<"b">}
+        : (!acc.private_type<memref<8xi32>>) -> memref<8xi32>
+    memref.store %c1_k, %a[%c0_k] : memref<4xi32>
+    memref.store %c1_k, %b[%c0_k] : memref<8xi32>
+    acc.yield
+  } <{origin = "acc.parallel"}>
+  return
+}
+
+// CHECK: Function=scalar
+// CHECK-NOT: Local memory used for
+func.func @scalar() {
+  %priv = acc.privatize par_dims(#acc<par_dims[thread_x]>)
+      : () -> !acc.private_type<memref<i32>>
+
+  acc.compute_region ins(%priv_in = %priv) : (!acc.private_type<memref<i32>>) {
+    %c1_k = arith.constant 1 : i32
+    %local = acc.private_local %priv_in {acc.var_name = #acc.var_name<"s">}
+        : (!acc.private_type<memref<i32>>) -> memref<i32>
+    memref.store %c1_k, %local[] : memref<i32>
+    acc.yield
+  } <{origin = "acc.parallel"}>
+  return
+}

>From 51aed1e4c83e03fbf57517854b5c87faf7980de6 Mon Sep 17 00:00:00 2001
From: Yian Su <yians at nvidia.com>
Date: Fri, 18 Sep 2026 07:32:39 -0700
Subject: [PATCH 2/2] [mlir][openacc] Report privates given thread-private
 storage

Drop the rank filter: register allocation happens after this pass, so
small arrays may be promoted and scalars may spill. Report the
allocation that was chosen instead, and skip privates whose name could
not be recovered.
---
 .../Dialect/OpenACC/Transforms/ACCCGToGPU.cpp | 42 +++++++++----------
 ...to-gpu-thread-private-storage-remark.mlir} |  7 ++--
 2 files changed, 22 insertions(+), 27 deletions(-)
 rename mlir/test/Dialect/OpenACC/{acc-cg-to-gpu-private-local-memory-remark.mlir => acc-cg-to-gpu-thread-private-storage-remark.mlir} (90%)

diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCCGToGPU.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCCGToGPU.cpp
index 59c5b68199d20..22aaed53b647d 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCCGToGPU.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCCGToGPU.cpp
@@ -529,11 +529,11 @@ class ACCCGToGPULowering {
   /// Reserve \p bytes from the shared-memory budget.
   bool tryAllocateSharedMemory(int64_t bytes);
 
-  /// Record the array behind \p privateLocal as per-thread local memory for
-  /// the closing remark. Rank-0 privatizations are skipped: a scalar is a
-  /// register candidate, not local memory.
-  void recordLocalMemPrivate(acc::PrivateLocalOp privateLocal,
-                             MemRefType baseTy);
+  /// Record \p privateLocal as given per-thread storage, for the closing
+  /// remark. Whether that storage lands in registers or in local memory is
+  /// decided later, so the remark reports the allocation, not the placement.
+  /// Privates with no recoverable name or compiler temps are skipped.
+  void recordThreadPrivate(acc::PrivateLocalOp privateLocal);
 
   /// Element size in bytes for \p elementType .
   int64_t getElementSizeInBytes(Location loc, Type elementType) const;
@@ -707,7 +707,7 @@ class ACCCGToGPULowering {
   acc::DefaultACCToGPUMappingPolicy defaultPolicy;
   SharedMemoryBudget sharedMemBudget;
   SmallVector<std::string> sharedMemPrivateVarNames;
-  SmallVector<std::string> localMemPrivateVarNames;
+  SmallVector<std::string> threadPrivateVarNames;
   llvm::SmallVector<Operation *, 4> deferredBarrierSeqLoops;
 
   Value getThreadId(Location loc, gpu::Dimension dim) {
@@ -872,18 +872,14 @@ bool ACCCGToGPULowering::tryAllocateSharedMemory(int64_t bytes) {
   return sharedMemBudget.tryAllocate(bytes);
 }
 
-void ACCCGToGPULowering::recordLocalMemPrivate(acc::PrivateLocalOp privateLocal,
-                                               MemRefType baseTy) {
-  if (baseTy.getRank() == 0)
-    return;
-  std::string varName =
-      privateLocal ? accSupport.getVariableName(privateLocal.getResult())
-                   : std::string();
+void ACCCGToGPULowering::recordThreadPrivate(acc::PrivateLocalOp privateLocal) {
+  std::string varName = accSupport.getVariableName(privateLocal.getResult());
+  // A name that could not be recovered, or a compiler temp are skipped.
   if (varName.empty())
-    varName = "(*)";
+    return;
   // The same variable reaches this point once per acc.private_local it has.
-  if (!llvm::is_contained(localMemPrivateVarNames, varName))
-    localMemPrivateVarNames.push_back(varName);
+  if (!llvm::is_contained(threadPrivateVarNames, varName))
+    threadPrivateVarNames.push_back(varName);
 }
 
 FailureOr<arith::AtomicRMWKind>
@@ -1271,10 +1267,10 @@ LogicalResult ACCCGToGPULowering::rewrite() {
   if (hasFailed)
     return failure();
 
-  if (!localMemPrivateVarNames.empty()) {
+  if (!threadPrivateVarNames.empty()) {
     accSupport.emitRemark(computeRegion, [&]() {
-      return (llvm::Twine("Local memory used for ") +
-              llvm::join(localMemPrivateVarNames, ","))
+      return (llvm::Twine("Thread-private storage used for ") +
+              llvm::join(threadPrivateVarNames, ","))
           .str();
     });
   }
@@ -2639,7 +2635,7 @@ Value ACCCGToGPULowering::processPrivatize(acc::PrivatizeOp privatize) {
   if (threadXActive &&
       canUseStackAlloca(baseTy, loc, options.maxThreadPrivateStack)) {
     auto alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
-    recordLocalMemPrivate(privateLocalUser, baseTy);
+    recordThreadPrivate(privateLocalUser);
     mapping.map(privatize.getResult(), alloca.getResult());
     return alloca.getResult();
   }
@@ -2698,7 +2694,7 @@ Value ACCCGToGPULowering::processPrivatize(acc::PrivatizeOp privatize) {
       // Static sizes: use alloca (stack allocation)
       auto alloca =
           memref::AllocaOp::create(rewriter, privatize->getLoc(), baseTy);
-      recordLocalMemPrivate(privateLocalUser, baseTy);
+      recordThreadPrivate(privateLocalUser);
       mapping.map(privatize.getResult(), alloca.getResult());
       return alloca.getResult();
     }
@@ -2885,7 +2881,7 @@ void ACCCGToGPULowering::processPrivateLocal(
          (arrayAccum && storageHasThreadX(privateLocal, computeRegion))) &&
         canUseStackAlloca(baseTy, loc, options.maxThreadPrivateStack)) {
       Value alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
-      recordLocalMemPrivate(privateLocal, baseTy);
+      recordThreadPrivate(privateLocal);
       if (arrayAccum) {
         FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
             arrayAccum.getReductionOperator(), baseTy.getElementType(), loc);
@@ -2976,7 +2972,7 @@ void ACCCGToGPULowering::processPrivateLocal(
          (arrayAccum && storageHasThreadX(privateLocal, computeRegion))) &&
         canUseStackAlloca(baseTy, loc, options.maxThreadPrivateStack)) {
       Value alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
-      recordLocalMemPrivate(privateLocal, baseTy);
+      recordThreadPrivate(privateLocal);
       if (arrayAccum) {
         FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
             arrayAccum.getReductionOperator(), baseTy.getElementType(), loc);
diff --git a/mlir/test/Dialect/OpenACC/acc-cg-to-gpu-private-local-memory-remark.mlir b/mlir/test/Dialect/OpenACC/acc-cg-to-gpu-thread-private-storage-remark.mlir
similarity index 90%
rename from mlir/test/Dialect/OpenACC/acc-cg-to-gpu-private-local-memory-remark.mlir
rename to mlir/test/Dialect/OpenACC/acc-cg-to-gpu-thread-private-storage-remark.mlir
index b69cf304d7fec..31ad3d5f384ed 100644
--- a/mlir/test/Dialect/OpenACC/acc-cg-to-gpu-private-local-memory-remark.mlir
+++ b/mlir/test/Dialect/OpenACC/acc-cg-to-gpu-thread-private-storage-remark.mlir
@@ -1,7 +1,7 @@
 // RUN: mlir-opt %s --pass-pipeline="builtin.module(func.func(acc-cg-to-gpu))" \
 // RUN:   --remarks-filter="(open)?acc.*" 2>&1 | FileCheck %s
 
-// CHECK: Function=one_array | Remark="Local memory used for buf"
+// CHECK: Function=one_array | Remark="Thread-private storage used for buf"
 func.func @one_array() {
   %c0 = arith.constant 0 : index
   %c1 = arith.constant 1 : i32
@@ -19,7 +19,7 @@ func.func @one_array() {
   return
 }
 
-// CHECK: Function=two_arrays | Remark="Local memory used for a,b"
+// CHECK: Function=two_arrays | Remark="Thread-private storage used for a,b"
 func.func @two_arrays() {
   %priv_a = acc.privatize par_dims(#acc<par_dims[thread_x]>)
       : () -> !acc.private_type<memref<4xi32>>
@@ -41,8 +41,7 @@ func.func @two_arrays() {
   return
 }
 
-// CHECK: Function=scalar
-// CHECK-NOT: Local memory used for
+// CHECK: Function=scalar | Remark="Thread-private storage used for s"
 func.func @scalar() {
   %priv = acc.privatize par_dims(#acc<par_dims[thread_x]>)
       : () -> !acc.private_type<memref<i32>>



More information about the Mlir-commits mailing list