-
Notifications
You must be signed in to change notification settings - Fork 14.9k
[mlir][acc] Add utilities for acc dialect #164022
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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.
@llvm/pr-subscribers-openacc @llvm/pr-subscribers-mlir-openacc Author: Razvan Lupusoru (razvanlupusoru) ChangesCreated 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:
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 ®ion);
-
/// 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 ®ion);
+
+/// Returns true if this value is only used by `acc.private` operations in the
+/// `region`.
+bool isOnlyUsedByPrivateClauses(mlir::Value val, mlir::Region ®ion);
+
+/// Returns true if this value is only used by `acc.reduction` operations in
+/// the `region`.
+bool isOnlyUsedByReductionClauses(mlir::Value val, mlir::Region ®ion);
+
+/// 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 ®ion) {
- 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 ®ion) {
+ 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 ®ion) {
+ 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 ®ion) {
+ return isOnlyUsedByOpClauses<mlir::acc::PrivateOp>(val, region);
+}
+
+bool mlir::acc::isOnlyUsedByReductionClauses(mlir::Value val,
+ mlir::Region ®ion) {
+ 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 ¶llelRegion = 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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.emplaceBlock();
+
+ OpBuilder::InsertionGuard guard(b);
+ ...
[truncated]
|
@llvm/pr-subscribers-mlir Author: Razvan Lupusoru (razvanlupusoru) ChangesCreated 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:
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 ®ion);
-
/// 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 ®ion);
+
+/// Returns true if this value is only used by `acc.private` operations in the
+/// `region`.
+bool isOnlyUsedByPrivateClauses(mlir::Value val, mlir::Region ®ion);
+
+/// Returns true if this value is only used by `acc.reduction` operations in
+/// the `region`.
+bool isOnlyUsedByReductionClauses(mlir::Value val, mlir::Region ®ion);
+
+/// 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 ®ion) {
- 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 ®ion) {
+ 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 ®ion) {
+ 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 ®ion) {
+ return isOnlyUsedByOpClauses<mlir::acc::PrivateOp>(val, region);
+}
+
+bool mlir::acc::isOnlyUsedByReductionClauses(mlir::Value val,
+ mlir::Region ®ion) {
+ 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 ¶llelRegion = 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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.emplaceBlock();
+
+ OpBuilder::InsertionGuard guard(b);
+ ...
[truncated]
|
@llvm/pr-subscribers-flang-fir-hlfir Author: Razvan Lupusoru (razvanlupusoru) ChangesCreated 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:
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 ®ion);
-
/// 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 ®ion);
+
+/// Returns true if this value is only used by `acc.private` operations in the
+/// `region`.
+bool isOnlyUsedByPrivateClauses(mlir::Value val, mlir::Region ®ion);
+
+/// Returns true if this value is only used by `acc.reduction` operations in
+/// the `region`.
+bool isOnlyUsedByReductionClauses(mlir::Value val, mlir::Region ®ion);
+
+/// 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 ®ion) {
- 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 ®ion) {
+ 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 ®ion) {
+ 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 ®ion) {
+ return isOnlyUsedByOpClauses<mlir::acc::PrivateOp>(val, region);
+}
+
+bool mlir::acc::isOnlyUsedByReductionClauses(mlir::Value val,
+ mlir::Region ®ion) {
+ 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 ¶llelRegion = 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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.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 ¶llelRegion = parallelOp->getRegion();
+ Block *parallelBlock = ¶llelRegion.emplaceBlock();
+
+ OpBuilder::InsertionGuard guard(b);
+ ...
[truncated]
|
if (mlir::isa<ACC_COMPUTE_CONSTRUCT_OPS>(parentOp)) { | ||
return parentOp; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no braces
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; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no braces
There was a problem hiding this 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.
There was a problem hiding this 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!
/// is found. The returns operation is one of types defined by | ||
///`ACC_COMPUTE_CONSTRUCT_OPS`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// 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`. |
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.