Skip to content

Conversation

razvanlupusoru
Copy link
Contributor

Created new OpenACC utilities library (MLIROpenACCUtils) containing helper functions for region analysis, value usage checking, default attribute lookup, and type categorization. Includes comprehensive unit tests and refactors existing getEnclosingComputeOp function into the new library.

Created new OpenACC utilities library (MLIROpenACCUtils) containing
helper functions for region analysis, value usage checking, default
attribute lookup, and type categorization. Includes comprehensive
unit tests and refactors existing getEnclosingComputeOp function
into the new library.
@llvmbot llvmbot added mlir flang Flang issues not falling into any other category mlir:openacc flang:fir-hlfir openacc labels Oct 17, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 17, 2025

@llvm/pr-subscribers-openacc

@llvm/pr-subscribers-mlir-openacc

Author: Razvan Lupusoru (razvanlupusoru)

Changes

Created new OpenACC utilities library (MLIROpenACCUtils) containing helper functions for region analysis, value usage checking, default attribute lookup, and type categorization. Includes comprehensive unit tests and refactors existing getEnclosingComputeOp function into the new library.


Patch is 25.46 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/164022.diff

9 Files Affected:

  • (modified) flang/lib/Lower/OpenACC.cpp (+1)
  • (modified) mlir/include/mlir/Dialect/OpenACC/OpenACC.h (-6)
  • (added) mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h (+44)
  • (modified) mlir/lib/Dialect/OpenACC/CMakeLists.txt (+1)
  • (modified) mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp (-11)
  • (added) mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt (+20)
  • (added) mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp (+83)
  • (modified) mlir/unittests/Dialect/OpenACC/CMakeLists.txt (+5)
  • (added) mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp (+412)
diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index cfb18914e8126..c5f25f6cda254 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -34,6 +34,7 @@
 #include "flang/Semantics/scope.h"
 #include "flang/Semantics/tools.h"
 #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
+#include "mlir/Dialect/OpenACC/OpenACCUtils.h"
 #include "mlir/IR/IRMapping.h"
 #include "mlir/IR/MLIRContext.h"
 #include "mlir/Support/LLVM.h"
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACC.h b/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
index b8aa49752d0a9..e2a60f57940f6 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
@@ -152,12 +152,6 @@ mlir::ValueRange getDataOperands(mlir::Operation *accOp);
 /// Used to get a mutable range iterating over the data operands.
 mlir::MutableOperandRange getMutableDataOperands(mlir::Operation *accOp);
 
-/// Used to obtain the enclosing compute construct operation that contains
-/// the provided `region`. Returns nullptr if no compute construct operation
-/// is found. The returns operation is one of types defined by
-///`ACC_COMPUTE_CONSTRUCT_OPS`.
-mlir::Operation *getEnclosingComputeOp(mlir::Region &region);
-
 /// Used to check whether the provided `type` implements the `PointerLikeType`
 /// interface.
 inline bool isPointerLikeType(mlir::Type type) {
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
new file mode 100644
index 0000000000000..69409e3030d81
--- /dev/null
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
@@ -0,0 +1,44 @@
+//===- OpenACCUtils.h - OpenACC Utilities -----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_DIALECT_OPENACC_OPENACCUTILS_H_
+#define MLIR_DIALECT_OPENACC_OPENACCUTILS_H_
+
+#include "mlir/Dialect/OpenACC/OpenACC.h"
+
+namespace mlir {
+namespace acc {
+
+/// Used to obtain the enclosing compute construct operation that contains
+/// the provided `region`. Returns nullptr if no compute construct operation
+/// is found. The returns operation is one of types defined by
+///`ACC_COMPUTE_CONSTRUCT_OPS`.
+mlir::Operation *getEnclosingComputeOp(mlir::Region &region);
+
+/// Returns true if this value is only used by `acc.private` operations in the
+/// `region`.
+bool isOnlyUsedByPrivateClauses(mlir::Value val, mlir::Region &region);
+
+/// Returns true if this value is only used by `acc.reduction` operations in
+/// the `region`.
+bool isOnlyUsedByReductionClauses(mlir::Value val, mlir::Region &region);
+
+/// Looks for an OpenACC default attribute on the current operation `op` or in
+/// a parent operation which encloses `op`. This is useful because OpenACC
+/// specification notes that a visible default clause is the nearest default
+/// clause appearing on the compute construct or a lexically containing data
+/// construct.
+std::optional<ClauseDefaultValue> getDefaultAttr(mlir::Operation *op);
+
+/// Get the type category of an OpenACC variable.
+mlir::acc::VariableTypeCategory getTypeCategory(mlir::Value var);
+
+} // namespace acc
+} // namespace mlir
+
+#endif // MLIR_DIALECT_OPENACC_OPENACCUTILS_H_
diff --git a/mlir/lib/Dialect/OpenACC/CMakeLists.txt b/mlir/lib/Dialect/OpenACC/CMakeLists.txt
index 9f57627c321fb..7117520599fa6 100644
--- a/mlir/lib/Dialect/OpenACC/CMakeLists.txt
+++ b/mlir/lib/Dialect/OpenACC/CMakeLists.txt
@@ -1,2 +1,3 @@
 add_subdirectory(IR)
+add_subdirectory(Utils)
 add_subdirectory(Transforms)
diff --git a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
index dcfe2c742407e..05a196a3f3b3c 100644
--- a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
+++ b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
@@ -4649,14 +4649,3 @@ mlir::acc::getMutableDataOperands(mlir::Operation *accOp) {
           .Default([&](mlir::Operation *) { return nullptr; })};
   return dataOperands;
 }
-
-mlir::Operation *mlir::acc::getEnclosingComputeOp(mlir::Region &region) {
-  mlir::Operation *parentOp = region.getParentOp();
-  while (parentOp) {
-    if (mlir::isa<ACC_COMPUTE_CONSTRUCT_OPS>(parentOp)) {
-      return parentOp;
-    }
-    parentOp = parentOp->getParentOp();
-  }
-  return nullptr;
-}
diff --git a/mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt b/mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt
new file mode 100644
index 0000000000000..68e124625921f
--- /dev/null
+++ b/mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt
@@ -0,0 +1,20 @@
+add_mlir_dialect_library(MLIROpenACCUtils
+  OpenACCUtils.cpp
+
+  ADDITIONAL_HEADER_DIRS
+  ${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/OpenACC
+
+  DEPENDS
+  MLIROpenACCPassIncGen
+  MLIROpenACCOpsIncGen
+  MLIROpenACCEnumsIncGen
+  MLIROpenACCAttributesIncGen
+  MLIROpenACCMPOpsInterfacesIncGen
+  MLIROpenACCOpsInterfacesIncGen
+  MLIROpenACCTypeInterfacesIncGen
+
+  LINK_LIBS PUBLIC
+  MLIROpenACCDialect
+  MLIRIR
+  MLIRSupport
+)
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
new file mode 100644
index 0000000000000..bac6b7a5ae273
--- /dev/null
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
@@ -0,0 +1,83 @@
+//===- OpenACCUtils.cpp ---------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/OpenACC/OpenACCUtils.h"
+
+#include "mlir/Dialect/OpenACC/OpenACC.h"
+#include "llvm/ADT/TypeSwitch.h"
+
+mlir::Operation *mlir::acc::getEnclosingComputeOp(mlir::Region &region) {
+  mlir::Operation *parentOp = region.getParentOp();
+  while (parentOp) {
+    if (mlir::isa<ACC_COMPUTE_CONSTRUCT_OPS>(parentOp)) {
+      return parentOp;
+    }
+    parentOp = parentOp->getParentOp();
+  }
+  return nullptr;
+}
+
+template <typename OpTy>
+static bool isOnlyUsedByOpClauses(mlir::Value val, mlir::Region &region) {
+  auto checkIfUsedOnlyByOpInside = [&](mlir::Operation *user) {
+    if (!region.isAncestor(user->getParentRegion())) {
+      // For any users which are not in the current acc region, we can ignore.
+      // Return true so that it can be used in a `all_of` check.
+      return true;
+    }
+    return mlir::isa<OpTy>(user);
+  };
+
+  return llvm::all_of(val.getUsers(), checkIfUsedOnlyByOpInside);
+}
+
+bool mlir::acc::isOnlyUsedByPrivateClauses(mlir::Value val,
+                                           mlir::Region &region) {
+  return isOnlyUsedByOpClauses<mlir::acc::PrivateOp>(val, region);
+}
+
+bool mlir::acc::isOnlyUsedByReductionClauses(mlir::Value val,
+                                             mlir::Region &region) {
+  return isOnlyUsedByOpClauses<mlir::acc::ReductionOp>(val, region);
+}
+
+std::optional<mlir::acc::ClauseDefaultValue>
+mlir::acc::getDefaultAttr(Operation *op) {
+  std::optional<mlir::acc::ClauseDefaultValue> defaultAttr;
+  Operation *currOp = op;
+
+  // Iterate outwards until a default clause is found (since OpenACC
+  // specification notes that a visible default clause is the nearest default
+  // clause appearing on the compute construct or a lexically containing data
+  // construct.
+  while (!defaultAttr.has_value() && currOp) {
+    defaultAttr =
+        llvm::TypeSwitch<mlir::Operation *,
+                         std::optional<mlir::acc::ClauseDefaultValue>>(currOp)
+            .Case<ACC_COMPUTE_CONSTRUCT_OPS, mlir::acc::DataOp>(
+                [&](auto op) { return op.getDefaultAttr(); })
+            .Default([&](Operation *) { return std::nullopt; });
+    currOp = currOp->getParentOp();
+  }
+
+  return defaultAttr;
+}
+
+mlir::acc::VariableTypeCategory mlir::acc::getTypeCategory(mlir::Value var) {
+  mlir::acc::VariableTypeCategory typeCategory =
+      mlir::acc::VariableTypeCategory::uncategorized;
+  if (auto mappableTy = dyn_cast<mlir::acc::MappableType>(var.getType())) {
+    typeCategory = mappableTy.getTypeCategory(var);
+  } else if (auto pointerLikeTy =
+                 dyn_cast<mlir::acc::PointerLikeType>(var.getType())) {
+    typeCategory = pointerLikeTy.getPointeeTypeCategory(
+        cast<TypedValue<mlir::acc::PointerLikeType>>(var),
+        pointerLikeTy.getElementType());
+  }
+  return typeCategory;
+}
diff --git a/mlir/unittests/Dialect/OpenACC/CMakeLists.txt b/mlir/unittests/Dialect/OpenACC/CMakeLists.txt
index d5f40a44f8cc6..177c8680b0040 100644
--- a/mlir/unittests/Dialect/OpenACC/CMakeLists.txt
+++ b/mlir/unittests/Dialect/OpenACC/CMakeLists.txt
@@ -1,8 +1,13 @@
 add_mlir_unittest(MLIROpenACCTests
   OpenACCOpsTest.cpp
+  OpenACCUtilsTest.cpp
 )
 mlir_target_link_libraries(MLIROpenACCTests
   PRIVATE
   MLIRIR
+  MLIRFuncDialect
+  MLIRMemRefDialect
+  MLIRArithDialect
   MLIROpenACCDialect
+  MLIROpenACCUtils
 )
diff --git a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp
new file mode 100644
index 0000000000000..ab817b640edb3
--- /dev/null
+++ b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp
@@ -0,0 +1,412 @@
+//===- OpenACCUtilsTest.cpp - Unit tests for OpenACC utilities -----------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/OpenACC/OpenACCUtils.h"
+#include "mlir/Dialect/Arith/IR/Arith.h"
+#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Dialect/MemRef/IR/MemRef.h"
+#include "mlir/Dialect/OpenACC/OpenACC.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/Diagnostics.h"
+#include "mlir/IR/MLIRContext.h"
+#include "mlir/IR/OwningOpRef.h"
+#include "mlir/IR/Value.h"
+#include "gtest/gtest.h"
+
+using namespace mlir;
+using namespace mlir::acc;
+
+//===----------------------------------------------------------------------===//
+// Test Fixture
+//===----------------------------------------------------------------------===//
+
+class OpenACCUtilsTest : public ::testing::Test {
+protected:
+  OpenACCUtilsTest() : b(&context), loc(UnknownLoc::get(&context)) {
+    context.loadDialect<acc::OpenACCDialect, arith::ArithDialect,
+                        memref::MemRefDialect, func::FuncDialect>();
+  }
+
+  MLIRContext context;
+  OpBuilder b;
+  Location loc;
+};
+
+//===----------------------------------------------------------------------===//
+// getEnclosingComputeOp Tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(OpenACCUtilsTest, getEnclosingComputeOpParallel) {
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  parallelRegion.emplaceBlock();
+
+  // Test that we can find the parallel op from its region
+  Operation *enclosingOp = getEnclosingComputeOp(parallelRegion);
+  EXPECT_EQ(enclosingOp, parallelOp.get());
+}
+
+TEST_F(OpenACCUtilsTest, getEnclosingComputeOpKernels) {
+  // Create a kernels op with a region
+  OwningOpRef<KernelsOp> kernelsOp =
+      KernelsOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &kernelsRegion = kernelsOp->getRegion();
+  kernelsRegion.emplaceBlock();
+
+  // Test that we can find the kernels op from its region
+  Operation *enclosingOp = getEnclosingComputeOp(kernelsRegion);
+  EXPECT_EQ(enclosingOp, kernelsOp.get());
+}
+
+TEST_F(OpenACCUtilsTest, getEnclosingComputeOpSerial) {
+  // Create a serial op with a region
+  OwningOpRef<SerialOp> serialOp =
+      SerialOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &serialRegion = serialOp->getRegion();
+  serialRegion.emplaceBlock();
+
+  // Test that we can find the serial op from its region
+  Operation *enclosingOp = getEnclosingComputeOp(serialRegion);
+  EXPECT_EQ(enclosingOp, serialOp.get());
+}
+
+TEST_F(OpenACCUtilsTest, getEnclosingComputeOpNested) {
+  // Create nested ops: parallel containing a loop op
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create a loop op inside the parallel region
+  OwningOpRef<LoopOp> loopOp =
+      LoopOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &loopRegion = loopOp->getRegion();
+  loopRegion.emplaceBlock();
+
+  // Test that from the loop region, we find the parallel op (loop is not a
+  // compute op)
+  Operation *enclosingOp = getEnclosingComputeOp(loopRegion);
+  EXPECT_EQ(enclosingOp, parallelOp.get());
+}
+
+TEST_F(OpenACCUtilsTest, getEnclosingComputeOpNone) {
+  // Create a module with a region that's not inside a compute construct
+  OwningOpRef<ModuleOp> moduleOp = ModuleOp::create(loc);
+  Region &moduleRegion = moduleOp->getBodyRegion();
+
+  // Test that we get nullptr when there's no enclosing compute op
+  Operation *enclosingOp = getEnclosingComputeOp(moduleRegion);
+  EXPECT_EQ(enclosingOp, nullptr);
+}
+
+//===----------------------------------------------------------------------===//
+// isOnlyUsedByPrivateClauses Tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByPrivateClausesTrue) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create a private op using the value
+  OwningOpRef<PrivateOp> privateOp = PrivateOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+
+  // Test that the value is only used by private clauses
+  EXPECT_TRUE(isOnlyUsedByPrivateClauses(varPtr, parallelRegion));
+}
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByPrivateClausesFalse) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create a private op using the value
+  OwningOpRef<PrivateOp> privateOp = PrivateOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+
+  // Also use the value in a function call (escape)
+  OwningOpRef<func::CallOp> callOp = func::CallOp::create(
+      b, loc, "some_func", TypeRange{}, ValueRange{varPtr});
+
+  // Test that the value is NOT only used by private clauses (it escapes via
+  // call)
+  EXPECT_FALSE(isOnlyUsedByPrivateClauses(varPtr, parallelRegion));
+}
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByPrivateClausesMultiple) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create multiple private ops using the value
+  OwningOpRef<PrivateOp> privateOp1 = PrivateOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+  OwningOpRef<PrivateOp> privateOp2 = PrivateOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+
+  // Test that the value is only used by private clauses even with multiple uses
+  EXPECT_TRUE(isOnlyUsedByPrivateClauses(varPtr, parallelRegion));
+}
+
+//===----------------------------------------------------------------------===//
+// isOnlyUsedByReductionClauses Tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByReductionClausesTrue) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create a reduction op using the value
+  OwningOpRef<ReductionOp> reductionOp = ReductionOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+
+  // Test that the value is only used by reduction clauses
+  EXPECT_TRUE(isOnlyUsedByReductionClauses(varPtr, parallelRegion));
+}
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByReductionClausesFalse) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create a reduction op using the value
+  OwningOpRef<ReductionOp> reductionOp = ReductionOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+
+  // Also use the value in a function call (escape)
+  OwningOpRef<func::CallOp> callOp = func::CallOp::create(
+      b, loc, "some_func", TypeRange{}, ValueRange{varPtr});
+
+  // Test that the value is NOT only used by reduction clauses (it escapes via
+  // call)
+  EXPECT_FALSE(isOnlyUsedByReductionClauses(varPtr, parallelRegion));
+}
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByReductionClausesMultiple) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  ...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Oct 17, 2025

@llvm/pr-subscribers-mlir

Author: Razvan Lupusoru (razvanlupusoru)

Changes

Created new OpenACC utilities library (MLIROpenACCUtils) containing helper functions for region analysis, value usage checking, default attribute lookup, and type categorization. Includes comprehensive unit tests and refactors existing getEnclosingComputeOp function into the new library.


Patch is 25.46 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/164022.diff

9 Files Affected:

  • (modified) flang/lib/Lower/OpenACC.cpp (+1)
  • (modified) mlir/include/mlir/Dialect/OpenACC/OpenACC.h (-6)
  • (added) mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h (+44)
  • (modified) mlir/lib/Dialect/OpenACC/CMakeLists.txt (+1)
  • (modified) mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp (-11)
  • (added) mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt (+20)
  • (added) mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp (+83)
  • (modified) mlir/unittests/Dialect/OpenACC/CMakeLists.txt (+5)
  • (added) mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp (+412)
diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index cfb18914e8126..c5f25f6cda254 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -34,6 +34,7 @@
 #include "flang/Semantics/scope.h"
 #include "flang/Semantics/tools.h"
 #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
+#include "mlir/Dialect/OpenACC/OpenACCUtils.h"
 #include "mlir/IR/IRMapping.h"
 #include "mlir/IR/MLIRContext.h"
 #include "mlir/Support/LLVM.h"
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACC.h b/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
index b8aa49752d0a9..e2a60f57940f6 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
@@ -152,12 +152,6 @@ mlir::ValueRange getDataOperands(mlir::Operation *accOp);
 /// Used to get a mutable range iterating over the data operands.
 mlir::MutableOperandRange getMutableDataOperands(mlir::Operation *accOp);
 
-/// Used to obtain the enclosing compute construct operation that contains
-/// the provided `region`. Returns nullptr if no compute construct operation
-/// is found. The returns operation is one of types defined by
-///`ACC_COMPUTE_CONSTRUCT_OPS`.
-mlir::Operation *getEnclosingComputeOp(mlir::Region &region);
-
 /// Used to check whether the provided `type` implements the `PointerLikeType`
 /// interface.
 inline bool isPointerLikeType(mlir::Type type) {
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
new file mode 100644
index 0000000000000..69409e3030d81
--- /dev/null
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
@@ -0,0 +1,44 @@
+//===- OpenACCUtils.h - OpenACC Utilities -----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_DIALECT_OPENACC_OPENACCUTILS_H_
+#define MLIR_DIALECT_OPENACC_OPENACCUTILS_H_
+
+#include "mlir/Dialect/OpenACC/OpenACC.h"
+
+namespace mlir {
+namespace acc {
+
+/// Used to obtain the enclosing compute construct operation that contains
+/// the provided `region`. Returns nullptr if no compute construct operation
+/// is found. The returns operation is one of types defined by
+///`ACC_COMPUTE_CONSTRUCT_OPS`.
+mlir::Operation *getEnclosingComputeOp(mlir::Region &region);
+
+/// Returns true if this value is only used by `acc.private` operations in the
+/// `region`.
+bool isOnlyUsedByPrivateClauses(mlir::Value val, mlir::Region &region);
+
+/// Returns true if this value is only used by `acc.reduction` operations in
+/// the `region`.
+bool isOnlyUsedByReductionClauses(mlir::Value val, mlir::Region &region);
+
+/// Looks for an OpenACC default attribute on the current operation `op` or in
+/// a parent operation which encloses `op`. This is useful because OpenACC
+/// specification notes that a visible default clause is the nearest default
+/// clause appearing on the compute construct or a lexically containing data
+/// construct.
+std::optional<ClauseDefaultValue> getDefaultAttr(mlir::Operation *op);
+
+/// Get the type category of an OpenACC variable.
+mlir::acc::VariableTypeCategory getTypeCategory(mlir::Value var);
+
+} // namespace acc
+} // namespace mlir
+
+#endif // MLIR_DIALECT_OPENACC_OPENACCUTILS_H_
diff --git a/mlir/lib/Dialect/OpenACC/CMakeLists.txt b/mlir/lib/Dialect/OpenACC/CMakeLists.txt
index 9f57627c321fb..7117520599fa6 100644
--- a/mlir/lib/Dialect/OpenACC/CMakeLists.txt
+++ b/mlir/lib/Dialect/OpenACC/CMakeLists.txt
@@ -1,2 +1,3 @@
 add_subdirectory(IR)
+add_subdirectory(Utils)
 add_subdirectory(Transforms)
diff --git a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
index dcfe2c742407e..05a196a3f3b3c 100644
--- a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
+++ b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
@@ -4649,14 +4649,3 @@ mlir::acc::getMutableDataOperands(mlir::Operation *accOp) {
           .Default([&](mlir::Operation *) { return nullptr; })};
   return dataOperands;
 }
-
-mlir::Operation *mlir::acc::getEnclosingComputeOp(mlir::Region &region) {
-  mlir::Operation *parentOp = region.getParentOp();
-  while (parentOp) {
-    if (mlir::isa<ACC_COMPUTE_CONSTRUCT_OPS>(parentOp)) {
-      return parentOp;
-    }
-    parentOp = parentOp->getParentOp();
-  }
-  return nullptr;
-}
diff --git a/mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt b/mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt
new file mode 100644
index 0000000000000..68e124625921f
--- /dev/null
+++ b/mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt
@@ -0,0 +1,20 @@
+add_mlir_dialect_library(MLIROpenACCUtils
+  OpenACCUtils.cpp
+
+  ADDITIONAL_HEADER_DIRS
+  ${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/OpenACC
+
+  DEPENDS
+  MLIROpenACCPassIncGen
+  MLIROpenACCOpsIncGen
+  MLIROpenACCEnumsIncGen
+  MLIROpenACCAttributesIncGen
+  MLIROpenACCMPOpsInterfacesIncGen
+  MLIROpenACCOpsInterfacesIncGen
+  MLIROpenACCTypeInterfacesIncGen
+
+  LINK_LIBS PUBLIC
+  MLIROpenACCDialect
+  MLIRIR
+  MLIRSupport
+)
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
new file mode 100644
index 0000000000000..bac6b7a5ae273
--- /dev/null
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
@@ -0,0 +1,83 @@
+//===- OpenACCUtils.cpp ---------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/OpenACC/OpenACCUtils.h"
+
+#include "mlir/Dialect/OpenACC/OpenACC.h"
+#include "llvm/ADT/TypeSwitch.h"
+
+mlir::Operation *mlir::acc::getEnclosingComputeOp(mlir::Region &region) {
+  mlir::Operation *parentOp = region.getParentOp();
+  while (parentOp) {
+    if (mlir::isa<ACC_COMPUTE_CONSTRUCT_OPS>(parentOp)) {
+      return parentOp;
+    }
+    parentOp = parentOp->getParentOp();
+  }
+  return nullptr;
+}
+
+template <typename OpTy>
+static bool isOnlyUsedByOpClauses(mlir::Value val, mlir::Region &region) {
+  auto checkIfUsedOnlyByOpInside = [&](mlir::Operation *user) {
+    if (!region.isAncestor(user->getParentRegion())) {
+      // For any users which are not in the current acc region, we can ignore.
+      // Return true so that it can be used in a `all_of` check.
+      return true;
+    }
+    return mlir::isa<OpTy>(user);
+  };
+
+  return llvm::all_of(val.getUsers(), checkIfUsedOnlyByOpInside);
+}
+
+bool mlir::acc::isOnlyUsedByPrivateClauses(mlir::Value val,
+                                           mlir::Region &region) {
+  return isOnlyUsedByOpClauses<mlir::acc::PrivateOp>(val, region);
+}
+
+bool mlir::acc::isOnlyUsedByReductionClauses(mlir::Value val,
+                                             mlir::Region &region) {
+  return isOnlyUsedByOpClauses<mlir::acc::ReductionOp>(val, region);
+}
+
+std::optional<mlir::acc::ClauseDefaultValue>
+mlir::acc::getDefaultAttr(Operation *op) {
+  std::optional<mlir::acc::ClauseDefaultValue> defaultAttr;
+  Operation *currOp = op;
+
+  // Iterate outwards until a default clause is found (since OpenACC
+  // specification notes that a visible default clause is the nearest default
+  // clause appearing on the compute construct or a lexically containing data
+  // construct.
+  while (!defaultAttr.has_value() && currOp) {
+    defaultAttr =
+        llvm::TypeSwitch<mlir::Operation *,
+                         std::optional<mlir::acc::ClauseDefaultValue>>(currOp)
+            .Case<ACC_COMPUTE_CONSTRUCT_OPS, mlir::acc::DataOp>(
+                [&](auto op) { return op.getDefaultAttr(); })
+            .Default([&](Operation *) { return std::nullopt; });
+    currOp = currOp->getParentOp();
+  }
+
+  return defaultAttr;
+}
+
+mlir::acc::VariableTypeCategory mlir::acc::getTypeCategory(mlir::Value var) {
+  mlir::acc::VariableTypeCategory typeCategory =
+      mlir::acc::VariableTypeCategory::uncategorized;
+  if (auto mappableTy = dyn_cast<mlir::acc::MappableType>(var.getType())) {
+    typeCategory = mappableTy.getTypeCategory(var);
+  } else if (auto pointerLikeTy =
+                 dyn_cast<mlir::acc::PointerLikeType>(var.getType())) {
+    typeCategory = pointerLikeTy.getPointeeTypeCategory(
+        cast<TypedValue<mlir::acc::PointerLikeType>>(var),
+        pointerLikeTy.getElementType());
+  }
+  return typeCategory;
+}
diff --git a/mlir/unittests/Dialect/OpenACC/CMakeLists.txt b/mlir/unittests/Dialect/OpenACC/CMakeLists.txt
index d5f40a44f8cc6..177c8680b0040 100644
--- a/mlir/unittests/Dialect/OpenACC/CMakeLists.txt
+++ b/mlir/unittests/Dialect/OpenACC/CMakeLists.txt
@@ -1,8 +1,13 @@
 add_mlir_unittest(MLIROpenACCTests
   OpenACCOpsTest.cpp
+  OpenACCUtilsTest.cpp
 )
 mlir_target_link_libraries(MLIROpenACCTests
   PRIVATE
   MLIRIR
+  MLIRFuncDialect
+  MLIRMemRefDialect
+  MLIRArithDialect
   MLIROpenACCDialect
+  MLIROpenACCUtils
 )
diff --git a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp
new file mode 100644
index 0000000000000..ab817b640edb3
--- /dev/null
+++ b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp
@@ -0,0 +1,412 @@
+//===- OpenACCUtilsTest.cpp - Unit tests for OpenACC utilities -----------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/OpenACC/OpenACCUtils.h"
+#include "mlir/Dialect/Arith/IR/Arith.h"
+#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Dialect/MemRef/IR/MemRef.h"
+#include "mlir/Dialect/OpenACC/OpenACC.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/Diagnostics.h"
+#include "mlir/IR/MLIRContext.h"
+#include "mlir/IR/OwningOpRef.h"
+#include "mlir/IR/Value.h"
+#include "gtest/gtest.h"
+
+using namespace mlir;
+using namespace mlir::acc;
+
+//===----------------------------------------------------------------------===//
+// Test Fixture
+//===----------------------------------------------------------------------===//
+
+class OpenACCUtilsTest : public ::testing::Test {
+protected:
+  OpenACCUtilsTest() : b(&context), loc(UnknownLoc::get(&context)) {
+    context.loadDialect<acc::OpenACCDialect, arith::ArithDialect,
+                        memref::MemRefDialect, func::FuncDialect>();
+  }
+
+  MLIRContext context;
+  OpBuilder b;
+  Location loc;
+};
+
+//===----------------------------------------------------------------------===//
+// getEnclosingComputeOp Tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(OpenACCUtilsTest, getEnclosingComputeOpParallel) {
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  parallelRegion.emplaceBlock();
+
+  // Test that we can find the parallel op from its region
+  Operation *enclosingOp = getEnclosingComputeOp(parallelRegion);
+  EXPECT_EQ(enclosingOp, parallelOp.get());
+}
+
+TEST_F(OpenACCUtilsTest, getEnclosingComputeOpKernels) {
+  // Create a kernels op with a region
+  OwningOpRef<KernelsOp> kernelsOp =
+      KernelsOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &kernelsRegion = kernelsOp->getRegion();
+  kernelsRegion.emplaceBlock();
+
+  // Test that we can find the kernels op from its region
+  Operation *enclosingOp = getEnclosingComputeOp(kernelsRegion);
+  EXPECT_EQ(enclosingOp, kernelsOp.get());
+}
+
+TEST_F(OpenACCUtilsTest, getEnclosingComputeOpSerial) {
+  // Create a serial op with a region
+  OwningOpRef<SerialOp> serialOp =
+      SerialOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &serialRegion = serialOp->getRegion();
+  serialRegion.emplaceBlock();
+
+  // Test that we can find the serial op from its region
+  Operation *enclosingOp = getEnclosingComputeOp(serialRegion);
+  EXPECT_EQ(enclosingOp, serialOp.get());
+}
+
+TEST_F(OpenACCUtilsTest, getEnclosingComputeOpNested) {
+  // Create nested ops: parallel containing a loop op
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create a loop op inside the parallel region
+  OwningOpRef<LoopOp> loopOp =
+      LoopOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &loopRegion = loopOp->getRegion();
+  loopRegion.emplaceBlock();
+
+  // Test that from the loop region, we find the parallel op (loop is not a
+  // compute op)
+  Operation *enclosingOp = getEnclosingComputeOp(loopRegion);
+  EXPECT_EQ(enclosingOp, parallelOp.get());
+}
+
+TEST_F(OpenACCUtilsTest, getEnclosingComputeOpNone) {
+  // Create a module with a region that's not inside a compute construct
+  OwningOpRef<ModuleOp> moduleOp = ModuleOp::create(loc);
+  Region &moduleRegion = moduleOp->getBodyRegion();
+
+  // Test that we get nullptr when there's no enclosing compute op
+  Operation *enclosingOp = getEnclosingComputeOp(moduleRegion);
+  EXPECT_EQ(enclosingOp, nullptr);
+}
+
+//===----------------------------------------------------------------------===//
+// isOnlyUsedByPrivateClauses Tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByPrivateClausesTrue) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create a private op using the value
+  OwningOpRef<PrivateOp> privateOp = PrivateOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+
+  // Test that the value is only used by private clauses
+  EXPECT_TRUE(isOnlyUsedByPrivateClauses(varPtr, parallelRegion));
+}
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByPrivateClausesFalse) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create a private op using the value
+  OwningOpRef<PrivateOp> privateOp = PrivateOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+
+  // Also use the value in a function call (escape)
+  OwningOpRef<func::CallOp> callOp = func::CallOp::create(
+      b, loc, "some_func", TypeRange{}, ValueRange{varPtr});
+
+  // Test that the value is NOT only used by private clauses (it escapes via
+  // call)
+  EXPECT_FALSE(isOnlyUsedByPrivateClauses(varPtr, parallelRegion));
+}
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByPrivateClausesMultiple) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create multiple private ops using the value
+  OwningOpRef<PrivateOp> privateOp1 = PrivateOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+  OwningOpRef<PrivateOp> privateOp2 = PrivateOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+
+  // Test that the value is only used by private clauses even with multiple uses
+  EXPECT_TRUE(isOnlyUsedByPrivateClauses(varPtr, parallelRegion));
+}
+
+//===----------------------------------------------------------------------===//
+// isOnlyUsedByReductionClauses Tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByReductionClausesTrue) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create a reduction op using the value
+  OwningOpRef<ReductionOp> reductionOp = ReductionOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+
+  // Test that the value is only used by reduction clauses
+  EXPECT_TRUE(isOnlyUsedByReductionClauses(varPtr, parallelRegion));
+}
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByReductionClausesFalse) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create a reduction op using the value
+  OwningOpRef<ReductionOp> reductionOp = ReductionOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+
+  // Also use the value in a function call (escape)
+  OwningOpRef<func::CallOp> callOp = func::CallOp::create(
+      b, loc, "some_func", TypeRange{}, ValueRange{varPtr});
+
+  // Test that the value is NOT only used by reduction clauses (it escapes via
+  // call)
+  EXPECT_FALSE(isOnlyUsedByReductionClauses(varPtr, parallelRegion));
+}
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByReductionClausesMultiple) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  ...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Oct 17, 2025

@llvm/pr-subscribers-flang-fir-hlfir

Author: Razvan Lupusoru (razvanlupusoru)

Changes

Created new OpenACC utilities library (MLIROpenACCUtils) containing helper functions for region analysis, value usage checking, default attribute lookup, and type categorization. Includes comprehensive unit tests and refactors existing getEnclosingComputeOp function into the new library.


Patch is 25.46 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/164022.diff

9 Files Affected:

  • (modified) flang/lib/Lower/OpenACC.cpp (+1)
  • (modified) mlir/include/mlir/Dialect/OpenACC/OpenACC.h (-6)
  • (added) mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h (+44)
  • (modified) mlir/lib/Dialect/OpenACC/CMakeLists.txt (+1)
  • (modified) mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp (-11)
  • (added) mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt (+20)
  • (added) mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp (+83)
  • (modified) mlir/unittests/Dialect/OpenACC/CMakeLists.txt (+5)
  • (added) mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp (+412)
diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index cfb18914e8126..c5f25f6cda254 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -34,6 +34,7 @@
 #include "flang/Semantics/scope.h"
 #include "flang/Semantics/tools.h"
 #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
+#include "mlir/Dialect/OpenACC/OpenACCUtils.h"
 #include "mlir/IR/IRMapping.h"
 #include "mlir/IR/MLIRContext.h"
 #include "mlir/Support/LLVM.h"
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACC.h b/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
index b8aa49752d0a9..e2a60f57940f6 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
@@ -152,12 +152,6 @@ mlir::ValueRange getDataOperands(mlir::Operation *accOp);
 /// Used to get a mutable range iterating over the data operands.
 mlir::MutableOperandRange getMutableDataOperands(mlir::Operation *accOp);
 
-/// Used to obtain the enclosing compute construct operation that contains
-/// the provided `region`. Returns nullptr if no compute construct operation
-/// is found. The returns operation is one of types defined by
-///`ACC_COMPUTE_CONSTRUCT_OPS`.
-mlir::Operation *getEnclosingComputeOp(mlir::Region &region);
-
 /// Used to check whether the provided `type` implements the `PointerLikeType`
 /// interface.
 inline bool isPointerLikeType(mlir::Type type) {
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
new file mode 100644
index 0000000000000..69409e3030d81
--- /dev/null
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
@@ -0,0 +1,44 @@
+//===- OpenACCUtils.h - OpenACC Utilities -----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_DIALECT_OPENACC_OPENACCUTILS_H_
+#define MLIR_DIALECT_OPENACC_OPENACCUTILS_H_
+
+#include "mlir/Dialect/OpenACC/OpenACC.h"
+
+namespace mlir {
+namespace acc {
+
+/// Used to obtain the enclosing compute construct operation that contains
+/// the provided `region`. Returns nullptr if no compute construct operation
+/// is found. The returns operation is one of types defined by
+///`ACC_COMPUTE_CONSTRUCT_OPS`.
+mlir::Operation *getEnclosingComputeOp(mlir::Region &region);
+
+/// Returns true if this value is only used by `acc.private` operations in the
+/// `region`.
+bool isOnlyUsedByPrivateClauses(mlir::Value val, mlir::Region &region);
+
+/// Returns true if this value is only used by `acc.reduction` operations in
+/// the `region`.
+bool isOnlyUsedByReductionClauses(mlir::Value val, mlir::Region &region);
+
+/// Looks for an OpenACC default attribute on the current operation `op` or in
+/// a parent operation which encloses `op`. This is useful because OpenACC
+/// specification notes that a visible default clause is the nearest default
+/// clause appearing on the compute construct or a lexically containing data
+/// construct.
+std::optional<ClauseDefaultValue> getDefaultAttr(mlir::Operation *op);
+
+/// Get the type category of an OpenACC variable.
+mlir::acc::VariableTypeCategory getTypeCategory(mlir::Value var);
+
+} // namespace acc
+} // namespace mlir
+
+#endif // MLIR_DIALECT_OPENACC_OPENACCUTILS_H_
diff --git a/mlir/lib/Dialect/OpenACC/CMakeLists.txt b/mlir/lib/Dialect/OpenACC/CMakeLists.txt
index 9f57627c321fb..7117520599fa6 100644
--- a/mlir/lib/Dialect/OpenACC/CMakeLists.txt
+++ b/mlir/lib/Dialect/OpenACC/CMakeLists.txt
@@ -1,2 +1,3 @@
 add_subdirectory(IR)
+add_subdirectory(Utils)
 add_subdirectory(Transforms)
diff --git a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
index dcfe2c742407e..05a196a3f3b3c 100644
--- a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
+++ b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
@@ -4649,14 +4649,3 @@ mlir::acc::getMutableDataOperands(mlir::Operation *accOp) {
           .Default([&](mlir::Operation *) { return nullptr; })};
   return dataOperands;
 }
-
-mlir::Operation *mlir::acc::getEnclosingComputeOp(mlir::Region &region) {
-  mlir::Operation *parentOp = region.getParentOp();
-  while (parentOp) {
-    if (mlir::isa<ACC_COMPUTE_CONSTRUCT_OPS>(parentOp)) {
-      return parentOp;
-    }
-    parentOp = parentOp->getParentOp();
-  }
-  return nullptr;
-}
diff --git a/mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt b/mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt
new file mode 100644
index 0000000000000..68e124625921f
--- /dev/null
+++ b/mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt
@@ -0,0 +1,20 @@
+add_mlir_dialect_library(MLIROpenACCUtils
+  OpenACCUtils.cpp
+
+  ADDITIONAL_HEADER_DIRS
+  ${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/OpenACC
+
+  DEPENDS
+  MLIROpenACCPassIncGen
+  MLIROpenACCOpsIncGen
+  MLIROpenACCEnumsIncGen
+  MLIROpenACCAttributesIncGen
+  MLIROpenACCMPOpsInterfacesIncGen
+  MLIROpenACCOpsInterfacesIncGen
+  MLIROpenACCTypeInterfacesIncGen
+
+  LINK_LIBS PUBLIC
+  MLIROpenACCDialect
+  MLIRIR
+  MLIRSupport
+)
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
new file mode 100644
index 0000000000000..bac6b7a5ae273
--- /dev/null
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
@@ -0,0 +1,83 @@
+//===- OpenACCUtils.cpp ---------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/OpenACC/OpenACCUtils.h"
+
+#include "mlir/Dialect/OpenACC/OpenACC.h"
+#include "llvm/ADT/TypeSwitch.h"
+
+mlir::Operation *mlir::acc::getEnclosingComputeOp(mlir::Region &region) {
+  mlir::Operation *parentOp = region.getParentOp();
+  while (parentOp) {
+    if (mlir::isa<ACC_COMPUTE_CONSTRUCT_OPS>(parentOp)) {
+      return parentOp;
+    }
+    parentOp = parentOp->getParentOp();
+  }
+  return nullptr;
+}
+
+template <typename OpTy>
+static bool isOnlyUsedByOpClauses(mlir::Value val, mlir::Region &region) {
+  auto checkIfUsedOnlyByOpInside = [&](mlir::Operation *user) {
+    if (!region.isAncestor(user->getParentRegion())) {
+      // For any users which are not in the current acc region, we can ignore.
+      // Return true so that it can be used in a `all_of` check.
+      return true;
+    }
+    return mlir::isa<OpTy>(user);
+  };
+
+  return llvm::all_of(val.getUsers(), checkIfUsedOnlyByOpInside);
+}
+
+bool mlir::acc::isOnlyUsedByPrivateClauses(mlir::Value val,
+                                           mlir::Region &region) {
+  return isOnlyUsedByOpClauses<mlir::acc::PrivateOp>(val, region);
+}
+
+bool mlir::acc::isOnlyUsedByReductionClauses(mlir::Value val,
+                                             mlir::Region &region) {
+  return isOnlyUsedByOpClauses<mlir::acc::ReductionOp>(val, region);
+}
+
+std::optional<mlir::acc::ClauseDefaultValue>
+mlir::acc::getDefaultAttr(Operation *op) {
+  std::optional<mlir::acc::ClauseDefaultValue> defaultAttr;
+  Operation *currOp = op;
+
+  // Iterate outwards until a default clause is found (since OpenACC
+  // specification notes that a visible default clause is the nearest default
+  // clause appearing on the compute construct or a lexically containing data
+  // construct.
+  while (!defaultAttr.has_value() && currOp) {
+    defaultAttr =
+        llvm::TypeSwitch<mlir::Operation *,
+                         std::optional<mlir::acc::ClauseDefaultValue>>(currOp)
+            .Case<ACC_COMPUTE_CONSTRUCT_OPS, mlir::acc::DataOp>(
+                [&](auto op) { return op.getDefaultAttr(); })
+            .Default([&](Operation *) { return std::nullopt; });
+    currOp = currOp->getParentOp();
+  }
+
+  return defaultAttr;
+}
+
+mlir::acc::VariableTypeCategory mlir::acc::getTypeCategory(mlir::Value var) {
+  mlir::acc::VariableTypeCategory typeCategory =
+      mlir::acc::VariableTypeCategory::uncategorized;
+  if (auto mappableTy = dyn_cast<mlir::acc::MappableType>(var.getType())) {
+    typeCategory = mappableTy.getTypeCategory(var);
+  } else if (auto pointerLikeTy =
+                 dyn_cast<mlir::acc::PointerLikeType>(var.getType())) {
+    typeCategory = pointerLikeTy.getPointeeTypeCategory(
+        cast<TypedValue<mlir::acc::PointerLikeType>>(var),
+        pointerLikeTy.getElementType());
+  }
+  return typeCategory;
+}
diff --git a/mlir/unittests/Dialect/OpenACC/CMakeLists.txt b/mlir/unittests/Dialect/OpenACC/CMakeLists.txt
index d5f40a44f8cc6..177c8680b0040 100644
--- a/mlir/unittests/Dialect/OpenACC/CMakeLists.txt
+++ b/mlir/unittests/Dialect/OpenACC/CMakeLists.txt
@@ -1,8 +1,13 @@
 add_mlir_unittest(MLIROpenACCTests
   OpenACCOpsTest.cpp
+  OpenACCUtilsTest.cpp
 )
 mlir_target_link_libraries(MLIROpenACCTests
   PRIVATE
   MLIRIR
+  MLIRFuncDialect
+  MLIRMemRefDialect
+  MLIRArithDialect
   MLIROpenACCDialect
+  MLIROpenACCUtils
 )
diff --git a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp
new file mode 100644
index 0000000000000..ab817b640edb3
--- /dev/null
+++ b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp
@@ -0,0 +1,412 @@
+//===- OpenACCUtilsTest.cpp - Unit tests for OpenACC utilities -----------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/OpenACC/OpenACCUtils.h"
+#include "mlir/Dialect/Arith/IR/Arith.h"
+#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Dialect/MemRef/IR/MemRef.h"
+#include "mlir/Dialect/OpenACC/OpenACC.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/Diagnostics.h"
+#include "mlir/IR/MLIRContext.h"
+#include "mlir/IR/OwningOpRef.h"
+#include "mlir/IR/Value.h"
+#include "gtest/gtest.h"
+
+using namespace mlir;
+using namespace mlir::acc;
+
+//===----------------------------------------------------------------------===//
+// Test Fixture
+//===----------------------------------------------------------------------===//
+
+class OpenACCUtilsTest : public ::testing::Test {
+protected:
+  OpenACCUtilsTest() : b(&context), loc(UnknownLoc::get(&context)) {
+    context.loadDialect<acc::OpenACCDialect, arith::ArithDialect,
+                        memref::MemRefDialect, func::FuncDialect>();
+  }
+
+  MLIRContext context;
+  OpBuilder b;
+  Location loc;
+};
+
+//===----------------------------------------------------------------------===//
+// getEnclosingComputeOp Tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(OpenACCUtilsTest, getEnclosingComputeOpParallel) {
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  parallelRegion.emplaceBlock();
+
+  // Test that we can find the parallel op from its region
+  Operation *enclosingOp = getEnclosingComputeOp(parallelRegion);
+  EXPECT_EQ(enclosingOp, parallelOp.get());
+}
+
+TEST_F(OpenACCUtilsTest, getEnclosingComputeOpKernels) {
+  // Create a kernels op with a region
+  OwningOpRef<KernelsOp> kernelsOp =
+      KernelsOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &kernelsRegion = kernelsOp->getRegion();
+  kernelsRegion.emplaceBlock();
+
+  // Test that we can find the kernels op from its region
+  Operation *enclosingOp = getEnclosingComputeOp(kernelsRegion);
+  EXPECT_EQ(enclosingOp, kernelsOp.get());
+}
+
+TEST_F(OpenACCUtilsTest, getEnclosingComputeOpSerial) {
+  // Create a serial op with a region
+  OwningOpRef<SerialOp> serialOp =
+      SerialOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &serialRegion = serialOp->getRegion();
+  serialRegion.emplaceBlock();
+
+  // Test that we can find the serial op from its region
+  Operation *enclosingOp = getEnclosingComputeOp(serialRegion);
+  EXPECT_EQ(enclosingOp, serialOp.get());
+}
+
+TEST_F(OpenACCUtilsTest, getEnclosingComputeOpNested) {
+  // Create nested ops: parallel containing a loop op
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create a loop op inside the parallel region
+  OwningOpRef<LoopOp> loopOp =
+      LoopOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &loopRegion = loopOp->getRegion();
+  loopRegion.emplaceBlock();
+
+  // Test that from the loop region, we find the parallel op (loop is not a
+  // compute op)
+  Operation *enclosingOp = getEnclosingComputeOp(loopRegion);
+  EXPECT_EQ(enclosingOp, parallelOp.get());
+}
+
+TEST_F(OpenACCUtilsTest, getEnclosingComputeOpNone) {
+  // Create a module with a region that's not inside a compute construct
+  OwningOpRef<ModuleOp> moduleOp = ModuleOp::create(loc);
+  Region &moduleRegion = moduleOp->getBodyRegion();
+
+  // Test that we get nullptr when there's no enclosing compute op
+  Operation *enclosingOp = getEnclosingComputeOp(moduleRegion);
+  EXPECT_EQ(enclosingOp, nullptr);
+}
+
+//===----------------------------------------------------------------------===//
+// isOnlyUsedByPrivateClauses Tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByPrivateClausesTrue) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create a private op using the value
+  OwningOpRef<PrivateOp> privateOp = PrivateOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+
+  // Test that the value is only used by private clauses
+  EXPECT_TRUE(isOnlyUsedByPrivateClauses(varPtr, parallelRegion));
+}
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByPrivateClausesFalse) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create a private op using the value
+  OwningOpRef<PrivateOp> privateOp = PrivateOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+
+  // Also use the value in a function call (escape)
+  OwningOpRef<func::CallOp> callOp = func::CallOp::create(
+      b, loc, "some_func", TypeRange{}, ValueRange{varPtr});
+
+  // Test that the value is NOT only used by private clauses (it escapes via
+  // call)
+  EXPECT_FALSE(isOnlyUsedByPrivateClauses(varPtr, parallelRegion));
+}
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByPrivateClausesMultiple) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create multiple private ops using the value
+  OwningOpRef<PrivateOp> privateOp1 = PrivateOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+  OwningOpRef<PrivateOp> privateOp2 = PrivateOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+
+  // Test that the value is only used by private clauses even with multiple uses
+  EXPECT_TRUE(isOnlyUsedByPrivateClauses(varPtr, parallelRegion));
+}
+
+//===----------------------------------------------------------------------===//
+// isOnlyUsedByReductionClauses Tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByReductionClausesTrue) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create a reduction op using the value
+  OwningOpRef<ReductionOp> reductionOp = ReductionOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+
+  // Test that the value is only used by reduction clauses
+  EXPECT_TRUE(isOnlyUsedByReductionClauses(varPtr, parallelRegion));
+}
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByReductionClausesFalse) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(parallelBlock);
+
+  // Create a reduction op using the value
+  OwningOpRef<ReductionOp> reductionOp = ReductionOp::create(
+      b, loc, varPtr, /*structured=*/true, /*implicit=*/false);
+
+  // Also use the value in a function call (escape)
+  OwningOpRef<func::CallOp> callOp = func::CallOp::create(
+      b, loc, "some_func", TypeRange{}, ValueRange{varPtr});
+
+  // Test that the value is NOT only used by reduction clauses (it escapes via
+  // call)
+  EXPECT_FALSE(isOnlyUsedByReductionClauses(varPtr, parallelRegion));
+}
+
+TEST_F(OpenACCUtilsTest, isOnlyUsedByReductionClausesMultiple) {
+  // Create a value (memref) outside the compute region
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  TypedValue<PointerLikeType> varPtr =
+      cast<TypedValue<PointerLikeType>>(allocOp->getResult());
+
+  // Create a parallel op with a region
+  OwningOpRef<ParallelOp> parallelOp =
+      ParallelOp::create(b, loc, TypeRange{}, ValueRange{});
+  Region &parallelRegion = parallelOp->getRegion();
+  Block *parallelBlock = &parallelRegion.emplaceBlock();
+
+  OpBuilder::InsertionGuard guard(b);
+  ...
[truncated]

Comment on lines 17 to 19
if (mlir::isa<ACC_COMPUTE_CONSTRUCT_OPS>(parentOp)) {
return parentOp;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no braces

Comment on lines 28 to 32
if (!region.isAncestor(user->getParentRegion())) {
// For any users which are not in the current acc region, we can ignore.
// Return true so that it can be used in a `all_of` check.
return true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no braces

Copy link
Contributor

@clementval clementval left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of formatting comments.

LGTM. Make sure it work with shared libs on.

Copy link
Contributor

@vzakhari vzakhari left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (modulo Valentin's comments). Thank you, Razvan!

Comment on lines 19 to 20
/// is found. The returns operation is one of types defined by
///`ACC_COMPUTE_CONSTRUCT_OPS`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// is found. The returns operation is one of types defined by
///`ACC_COMPUTE_CONSTRUCT_OPS`.
/// is found. The returned operation is one of types defined by
/// `ACC_COMPUTE_CONSTRUCT_OPS`.

@razvanlupusoru razvanlupusoru merged commit 2ecf122 into llvm:main Oct 20, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

flang:fir-hlfir flang Flang issues not falling into any other category mlir:openacc mlir openacc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants