diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h index 2852e0917c3fb..e9ce9b3a36aba 100644 --- a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h +++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h @@ -10,8 +10,11 @@ #define MLIR_DIALECT_OPENACC_OPENACCUTILS_H_ #include "mlir/Dialect/OpenACC/OpenACC.h" +#include "llvm/ADT/SmallVector.h" namespace mlir { +class DominanceInfo; +class PostDominanceInfo; namespace acc { /// Used to obtain the enclosing compute construct operation that contains @@ -62,6 +65,22 @@ mlir::Value getBaseEntity(mlir::Value val); bool isValidSymbolUse(mlir::Operation *user, mlir::SymbolRefAttr symbol, mlir::Operation **definingOpPtr = nullptr); +/// Collects all data clauses that dominate the compute construct. +/// This includes data clauses from: +/// - The compute construct itself +/// - Enclosing data constructs +/// - Applicable declare directives (those that dominate and post-dominate) +/// This is used to determine if a variable is already covered by an existing +/// data clause. +/// \param computeConstructOp The compute construct operation +/// \param domInfo Dominance information +/// \param postDomInfo Post-dominance information +/// \return Vector of data clause values that dominate the compute construct +llvm::SmallVector +getDominatingDataClauses(mlir::Operation *computeConstructOp, + mlir::DominanceInfo &domInfo, + mlir::PostDominanceInfo &postDomInfo); + } // namespace acc } // namespace mlir diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitData.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitData.cpp index 8bdde7c9691bf..67cdf100a7a48 100644 --- a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitData.cpp +++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitData.cpp @@ -237,11 +237,6 @@ class ACCImplicitData : public acc::impl::ACCImplicitDataBase { void runOnOperation() override; private: - /// Collects all data clauses that dominate the compute construct. - /// Needed to determine if a variable is already covered by an existing data - /// clause. - SmallVector getDominatingDataClauses(Operation *computeConstructOp); - /// Looks through the `dominatingDataClauses` to find the original data clause /// op for an alias. Returns nullptr if no original data clause op is found. template @@ -300,62 +295,6 @@ static bool isCandidateForImplicitData(Value val, Region &accRegion) { return true; } -SmallVector -ACCImplicitData::getDominatingDataClauses(Operation *computeConstructOp) { - llvm::SmallSetVector dominatingDataClauses; - - llvm::TypeSwitch(computeConstructOp) - .Case([&](auto op) { - for (auto dataClause : op.getDataClauseOperands()) { - dominatingDataClauses.insert(dataClause); - } - }) - .Default([](Operation *) {}); - - // Collect the data clauses from enclosing data constructs. - Operation *currParentOp = computeConstructOp->getParentOp(); - while (currParentOp) { - if (isa(currParentOp)) { - for (auto dataClause : - dyn_cast(currParentOp).getDataClauseOperands()) { - dominatingDataClauses.insert(dataClause); - } - } - currParentOp = currParentOp->getParentOp(); - } - - // Find the enclosing function/subroutine - auto funcOp = computeConstructOp->getParentOfType(); - if (!funcOp) - return dominatingDataClauses.takeVector(); - - // Walk the function to find `acc.declare_enter`/`acc.declare_exit` pairs that - // dominate and post-dominate the compute construct and add their data - // clauses to the list. - auto &domInfo = this->getAnalysis(); - auto &postDomInfo = this->getAnalysis(); - funcOp->walk([&](acc::DeclareEnterOp declareEnterOp) { - if (domInfo.dominates(declareEnterOp.getOperation(), computeConstructOp)) { - // Collect all `acc.declare_exit` ops for this token. - SmallVector exits; - for (auto *user : declareEnterOp.getToken().getUsers()) - if (auto declareExit = dyn_cast(user)) - exits.push_back(declareExit); - - // Only add clauses if every `acc.declare_exit` op post-dominates the - // compute construct. - if (!exits.empty() && llvm::all_of(exits, [&](acc::DeclareExitOp exitOp) { - return postDomInfo.postDominates(exitOp, computeConstructOp); - })) { - for (auto dataClause : declareEnterOp.getDataClauseOperands()) - dominatingDataClauses.insert(dataClause); - } - } - }); - - return dominatingDataClauses.takeVector(); -} - template Operation *ACCImplicitData::getOriginalDataClauseOpForAlias( Value var, OpBuilder &builder, OpT computeConstructOp, @@ -775,7 +714,10 @@ void ACCImplicitData::generateImplicitDataOps( LLVM_DEBUG(llvm::dbgs() << "== Generating clauses for ==\n" << computeConstructOp << "\n"); } - auto dominatingDataClauses = getDominatingDataClauses(computeConstructOp); + auto &domInfo = this->getAnalysis(); + auto &postDomInfo = this->getAnalysis(); + auto dominatingDataClauses = + acc::getDominatingDataClauses(computeConstructOp, domInfo, postDomInfo); for (auto var : candidateVars) { auto newDataClauseOp = generateDataClauseOpForCandidate( var, module, builder, computeConstructOp, dominatingDataClauses, diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp index aebc248e02ea0..7f27b4495045f 100644 --- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp +++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp @@ -9,9 +9,11 @@ #include "mlir/Dialect/OpenACC/OpenACCUtils.h" #include "mlir/Dialect/OpenACC/OpenACC.h" +#include "mlir/IR/Dominance.h" #include "mlir/IR/SymbolTable.h" #include "mlir/Interfaces/FunctionInterfaces.h" #include "mlir/Interfaces/ViewLikeInterface.h" +#include "llvm/ADT/SetVector.h" #include "llvm/ADT/TypeSwitch.h" #include "llvm/IR/Intrinsics.h" #include "llvm/Support/Casting.h" @@ -205,3 +207,62 @@ bool mlir::acc::isValidSymbolUse(mlir::Operation *user, bool hasDeclare = definingOp->hasAttr(mlir::acc::getDeclareAttrName()); return hasDeclare; } + +llvm::SmallVector +mlir::acc::getDominatingDataClauses(mlir::Operation *computeConstructOp, + mlir::DominanceInfo &domInfo, + mlir::PostDominanceInfo &postDomInfo) { + llvm::SmallSetVector dominatingDataClauses; + + llvm::TypeSwitch(computeConstructOp) + .Case( + [&](auto op) { + for (auto dataClause : op.getDataClauseOperands()) { + dominatingDataClauses.insert(dataClause); + } + }) + .Default([](mlir::Operation *) {}); + + // Collect the data clauses from enclosing data constructs. + mlir::Operation *currParentOp = computeConstructOp->getParentOp(); + while (currParentOp) { + if (mlir::isa(currParentOp)) { + for (auto dataClause : mlir::dyn_cast(currParentOp) + .getDataClauseOperands()) { + dominatingDataClauses.insert(dataClause); + } + } + currParentOp = currParentOp->getParentOp(); + } + + // Find the enclosing function/subroutine + auto funcOp = + computeConstructOp->getParentOfType(); + if (!funcOp) + return dominatingDataClauses.takeVector(); + + // Walk the function to find `acc.declare_enter`/`acc.declare_exit` pairs that + // dominate and post-dominate the compute construct and add their data + // clauses to the list. + funcOp->walk([&](mlir::acc::DeclareEnterOp declareEnterOp) { + if (domInfo.dominates(declareEnterOp.getOperation(), computeConstructOp)) { + // Collect all `acc.declare_exit` ops for this token. + llvm::SmallVector exits; + for (auto *user : declareEnterOp.getToken().getUsers()) + if (auto declareExit = mlir::dyn_cast(user)) + exits.push_back(declareExit); + + // Only add clauses if every `acc.declare_exit` op post-dominates the + // compute construct. + if (!exits.empty() && + llvm::all_of(exits, [&](mlir::acc::DeclareExitOp exitOp) { + return postDomInfo.postDominates(exitOp, computeConstructOp); + })) { + for (auto dataClause : declareEnterOp.getDataClauseOperands()) + dominatingDataClauses.insert(dataClause); + } + } + }); + + return dominatingDataClauses.takeVector(); +} diff --git a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp index 8b1f532bbe5c0..60d87326c0e9b 100644 --- a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp +++ b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp @@ -913,3 +913,457 @@ TEST_F(OpenACCUtilsTest, isValidSymbolUseNullDefiningOpPtr) { EXPECT_TRUE(result); } + +//===----------------------------------------------------------------------===// +// getDominatingDataClauses Tests +//===----------------------------------------------------------------------===// + +TEST_F(OpenACCUtilsTest, getDominatingDataClausesFromComputeConstruct) { + // Create a module to hold a function + OwningOpRef module = ModuleOp::create(loc); + Block *moduleBlock = module->getBody(); + + OpBuilder::InsertionGuard guard(b); + b.setInsertionPointToStart(moduleBlock); + + // Create a function + auto funcType = b.getFunctionType({}, {}); + OwningOpRef funcOp = + func::FuncOp::create(b, loc, "test_func", funcType); + Block *funcBlock = funcOp->addEntryBlock(); + + b.setInsertionPointToStart(funcBlock); + + // Create a memref for the data clause + auto memrefTy = MemRefType::get({10}, b.getI32Type()); + OwningOpRef allocOp = + memref::AllocaOp::create(b, loc, memrefTy); + TypedValue varPtr = + cast>(allocOp->getResult()); + + // Create a copyin op to represent a data clause + OwningOpRef copyinOp = + CopyinOp::create(b, loc, varPtr, /*structured=*/true, /*implicit=*/false, + /*name=*/"test_var"); + + // Create a parallel op + OwningOpRef parallelOp = + ParallelOp::create(b, loc, TypeRange{}, ValueRange{}); + + // Set the data clause operands + parallelOp->getDataClauseOperandsMutable().append(copyinOp->getAccVar()); + + // Create dominance info + DominanceInfo domInfo(funcOp.get()); + PostDominanceInfo postDomInfo(funcOp.get()); + + // Get dominating data clauses + auto dataClauses = + getDominatingDataClauses(parallelOp.get(), domInfo, postDomInfo); + + // Should contain the copyin from the parallel op + EXPECT_EQ(dataClauses.size(), 1ul); + EXPECT_EQ(dataClauses[0], copyinOp->getAccVar()); +} + +TEST_F(OpenACCUtilsTest, getDominatingDataClausesFromEnclosingDataOp) { + // Create a module to hold a function + OwningOpRef module = ModuleOp::create(loc); + Block *moduleBlock = module->getBody(); + + OpBuilder::InsertionGuard guard(b); + b.setInsertionPointToStart(moduleBlock); + + // Create a function + auto funcType = b.getFunctionType({}, {}); + OwningOpRef funcOp = + func::FuncOp::create(b, loc, "test_func", funcType); + Block *funcBlock = funcOp->addEntryBlock(); + + b.setInsertionPointToStart(funcBlock); + + // Create a memref for the data clause + auto memrefTy = MemRefType::get({10}, b.getI32Type()); + OwningOpRef allocOp = + memref::AllocaOp::create(b, loc, memrefTy); + TypedValue varPtr = + cast>(allocOp->getResult()); + + // Create a copyin op for the data construct + OwningOpRef copyinOp = + CopyinOp::create(b, loc, varPtr, /*structured=*/true, /*implicit=*/false, + /*name=*/"test_var"); + + // Create a data op + OwningOpRef dataOp = + DataOp::create(b, loc, TypeRange{}, ValueRange{}); + + // Set the data clause operands + dataOp->getDataClauseOperandsMutable().append(copyinOp->getAccVar()); + + Region &dataRegion = dataOp->getRegion(); + Block *dataBlock = &dataRegion.emplaceBlock(); + + b.setInsertionPointToStart(dataBlock); + + // Create a parallel op inside the data region (no data clauses on parallel) + OwningOpRef parallelOp = + ParallelOp::create(b, loc, TypeRange{}, ValueRange{}); + + // Create dominance info + DominanceInfo domInfo(funcOp.get()); + PostDominanceInfo postDomInfo(funcOp.get()); + + // Get dominating data clauses + auto dataClauses = + getDominatingDataClauses(parallelOp.get(), domInfo, postDomInfo); + + // Should contain the copyin from the enclosing data op + EXPECT_EQ(dataClauses.size(), 1ul); + EXPECT_EQ(dataClauses[0], copyinOp->getAccVar()); +} + +TEST_F(OpenACCUtilsTest, getDominatingDataClausesFromComputeAndEnclosingData) { + // Create a module to hold a function + OwningOpRef module = ModuleOp::create(loc); + Block *moduleBlock = module->getBody(); + + OpBuilder::InsertionGuard guard(b); + b.setInsertionPointToStart(moduleBlock); + + // Create a function + auto funcType = b.getFunctionType({}, {}); + OwningOpRef funcOp = + func::FuncOp::create(b, loc, "test_func", funcType); + Block *funcBlock = funcOp->addEntryBlock(); + + b.setInsertionPointToStart(funcBlock); + + // Create two memrefs for different data clauses + auto memrefTy = MemRefType::get({10}, b.getI32Type()); + OwningOpRef allocOp1 = + memref::AllocaOp::create(b, loc, memrefTy); + TypedValue varPtr1 = + cast>(allocOp1->getResult()); + + OwningOpRef allocOp2 = + memref::AllocaOp::create(b, loc, memrefTy); + TypedValue varPtr2 = + cast>(allocOp2->getResult()); + + // Create copyin ops + OwningOpRef copyinOp1 = + CopyinOp::create(b, loc, varPtr1, /*structured=*/true, /*implicit=*/false, + /*name=*/"var1"); + OwningOpRef copyinOp2 = + CopyinOp::create(b, loc, varPtr2, /*structured=*/true, /*implicit=*/false, + /*name=*/"var2"); + + // Create a data op + OwningOpRef dataOp = + DataOp::create(b, loc, TypeRange{}, ValueRange{}); + + // Set the data clause operands for data op + dataOp->getDataClauseOperandsMutable().append(copyinOp1->getAccVar()); + + Region &dataRegion = dataOp->getRegion(); + Block *dataBlock = &dataRegion.emplaceBlock(); + + b.setInsertionPointToStart(dataBlock); + + // Create a parallel op inside the data region + OwningOpRef parallelOp = + ParallelOp::create(b, loc, TypeRange{}, ValueRange{}); + + // Set the data clause operands for parallel op + parallelOp->getDataClauseOperandsMutable().append(copyinOp2->getAccVar()); + + // Create dominance info + DominanceInfo domInfo(funcOp.get()); + PostDominanceInfo postDomInfo(funcOp.get()); + + // Get dominating data clauses + auto dataClauses = + getDominatingDataClauses(parallelOp.get(), domInfo, postDomInfo); + + // Should contain both copyins (from data op and parallel op) + EXPECT_EQ(dataClauses.size(), 2ul); + // Note: Order might not be guaranteed, so check both are present + EXPECT_TRUE(llvm::is_contained(dataClauses, copyinOp1->getAccVar())); + EXPECT_TRUE(llvm::is_contained(dataClauses, copyinOp2->getAccVar())); +} + +TEST_F(OpenACCUtilsTest, getDominatingDataClausesWithDeclareDirectives) { + // Create a module to hold a function + OwningOpRef module = ModuleOp::create(loc); + Block *moduleBlock = module->getBody(); + + OpBuilder::InsertionGuard guard(b); + b.setInsertionPointToStart(moduleBlock); + + // Create a function + auto funcType = b.getFunctionType({}, {}); + OwningOpRef funcOp = + func::FuncOp::create(b, loc, "test_func", funcType); + Block *funcBlock = funcOp->addEntryBlock(); + + b.setInsertionPointToStart(funcBlock); + + // Create a memref for the declare directive + auto memrefTy = MemRefType::get({10}, b.getI32Type()); + OwningOpRef allocOp = + memref::AllocaOp::create(b, loc, memrefTy); + TypedValue varPtr = + cast>(allocOp->getResult()); + + // Create a copyin op for declare + OwningOpRef copyinOp = + CopyinOp::create(b, loc, varPtr, /*structured=*/false, /*implicit=*/false, + /*name=*/"declare_var"); + + // Create a declare_enter op + OwningOpRef declareEnterOp = DeclareEnterOp::create( + b, loc, TypeRange{b.getType()}, + ValueRange{copyinOp->getAccVar()}); + + // Create a parallel op + OwningOpRef parallelOp = + ParallelOp::create(b, loc, TypeRange{}, ValueRange{}); + + // Create a declare_exit op that post-dominates the parallel + OwningOpRef declareExitOp = DeclareExitOp::create( + b, loc, declareEnterOp->getToken(), ValueRange{copyinOp->getAccVar()}); + + // Add a return to complete the function + OwningOpRef returnOp = func::ReturnOp::create(b, loc); + + // Create dominance info + DominanceInfo domInfo(funcOp.get()); + PostDominanceInfo postDomInfo(funcOp.get()); + + // Get dominating data clauses + auto dataClauses = + getDominatingDataClauses(parallelOp.get(), domInfo, postDomInfo); + + // Should contain the copyin from the declare directive + EXPECT_EQ(dataClauses.size(), 1ul); + EXPECT_EQ(dataClauses[0], copyinOp->getAccVar()); +} + +TEST_F(OpenACCUtilsTest, getDominatingDataClausesMultipleDataConstructs) { + // Create a module to hold a function + OwningOpRef module = ModuleOp::create(loc); + Block *moduleBlock = module->getBody(); + + OpBuilder::InsertionGuard guard(b); + b.setInsertionPointToStart(moduleBlock); + + // Create a function + auto funcType = b.getFunctionType({}, {}); + OwningOpRef funcOp = + func::FuncOp::create(b, loc, "test_func", funcType); + Block *funcBlock = funcOp->addEntryBlock(); + + b.setInsertionPointToStart(funcBlock); + + // Create three memrefs + auto memrefTy = MemRefType::get({10}, b.getI32Type()); + OwningOpRef allocOp1 = + memref::AllocaOp::create(b, loc, memrefTy); + TypedValue varPtr1 = + cast>(allocOp1->getResult()); + + OwningOpRef allocOp2 = + memref::AllocaOp::create(b, loc, memrefTy); + TypedValue varPtr2 = + cast>(allocOp2->getResult()); + + OwningOpRef allocOp3 = + memref::AllocaOp::create(b, loc, memrefTy); + TypedValue varPtr3 = + cast>(allocOp3->getResult()); + + // Create copyin ops + OwningOpRef copyinOp1 = + CopyinOp::create(b, loc, varPtr1, /*structured=*/true, /*implicit=*/false, + /*name=*/"var1"); + OwningOpRef copyinOp2 = + CopyinOp::create(b, loc, varPtr2, /*structured=*/true, /*implicit=*/false, + /*name=*/"var2"); + OwningOpRef copyinOp3 = + CopyinOp::create(b, loc, varPtr3, /*structured=*/true, /*implicit=*/false, + /*name=*/"var3"); + + // Create outer data op + OwningOpRef outerDataOp = + DataOp::create(b, loc, TypeRange{}, ValueRange{}); + + // Set the data clause operands for outer data op + outerDataOp->getDataClauseOperandsMutable().append(copyinOp1->getAccVar()); + + Region &outerDataRegion = outerDataOp->getRegion(); + Block *outerDataBlock = &outerDataRegion.emplaceBlock(); + + b.setInsertionPointToStart(outerDataBlock); + + // Create inner data op + OwningOpRef innerDataOp = + DataOp::create(b, loc, TypeRange{}, ValueRange{}); + + // Set the data clause operands for inner data op + innerDataOp->getDataClauseOperandsMutable().append(copyinOp2->getAccVar()); + + Region &innerDataRegion = innerDataOp->getRegion(); + Block *innerDataBlock = &innerDataRegion.emplaceBlock(); + + b.setInsertionPointToStart(innerDataBlock); + + // Create a parallel op + OwningOpRef parallelOp = + ParallelOp::create(b, loc, TypeRange{}, ValueRange{}); + + // Set the data clause operands for parallel op + parallelOp->getDataClauseOperandsMutable().append(copyinOp3->getAccVar()); + + // Create dominance info + DominanceInfo domInfo(funcOp.get()); + PostDominanceInfo postDomInfo(funcOp.get()); + + // Get dominating data clauses + auto dataClauses = + getDominatingDataClauses(parallelOp.get(), domInfo, postDomInfo); + + // Should contain all three copyins + EXPECT_EQ(dataClauses.size(), 3ul); + EXPECT_TRUE(llvm::is_contained(dataClauses, copyinOp1->getAccVar())); + EXPECT_TRUE(llvm::is_contained(dataClauses, copyinOp2->getAccVar())); + EXPECT_TRUE(llvm::is_contained(dataClauses, copyinOp3->getAccVar())); +} + +TEST_F(OpenACCUtilsTest, getDominatingDataClausesKernelsOp) { + // Test with KernelsOp instead of ParallelOp + OwningOpRef module = ModuleOp::create(loc); + Block *moduleBlock = module->getBody(); + + OpBuilder::InsertionGuard guard(b); + b.setInsertionPointToStart(moduleBlock); + + // Create a function + auto funcType = b.getFunctionType({}, {}); + OwningOpRef funcOp = + func::FuncOp::create(b, loc, "test_func", funcType); + Block *funcBlock = funcOp->addEntryBlock(); + + b.setInsertionPointToStart(funcBlock); + + // Create a memref + auto memrefTy = MemRefType::get({10}, b.getI32Type()); + OwningOpRef allocOp = + memref::AllocaOp::create(b, loc, memrefTy); + TypedValue varPtr = + cast>(allocOp->getResult()); + + // Create a copyin op + OwningOpRef copyinOp = + CopyinOp::create(b, loc, varPtr, /*structured=*/true, /*implicit=*/false, + /*name=*/"test_var"); + + // Create a kernels op + OwningOpRef kernelsOp = + KernelsOp::create(b, loc, TypeRange{}, ValueRange{}); + + // Set the data clause operands + kernelsOp->getDataClauseOperandsMutable().append(copyinOp->getAccVar()); + + // Create dominance info + DominanceInfo domInfo(funcOp.get()); + PostDominanceInfo postDomInfo(funcOp.get()); + + // Get dominating data clauses + auto dataClauses = + getDominatingDataClauses(kernelsOp.get(), domInfo, postDomInfo); + + // Should contain the copyin from the kernels op + EXPECT_EQ(dataClauses.size(), 1ul); + EXPECT_EQ(dataClauses[0], copyinOp->getAccVar()); +} + +TEST_F(OpenACCUtilsTest, getDominatingDataClausesSerialOp) { + // Test with SerialOp + OwningOpRef module = ModuleOp::create(loc); + Block *moduleBlock = module->getBody(); + + OpBuilder::InsertionGuard guard(b); + b.setInsertionPointToStart(moduleBlock); + + // Create a function + auto funcType = b.getFunctionType({}, {}); + OwningOpRef funcOp = + func::FuncOp::create(b, loc, "test_func", funcType); + Block *funcBlock = funcOp->addEntryBlock(); + + b.setInsertionPointToStart(funcBlock); + + // Create a memref + auto memrefTy = MemRefType::get({10}, b.getI32Type()); + OwningOpRef allocOp = + memref::AllocaOp::create(b, loc, memrefTy); + TypedValue varPtr = + cast>(allocOp->getResult()); + + // Create a copyin op + OwningOpRef copyinOp = + CopyinOp::create(b, loc, varPtr, /*structured=*/true, /*implicit=*/false, + /*name=*/"test_var"); + + // Create a serial op + OwningOpRef serialOp = + SerialOp::create(b, loc, TypeRange{}, ValueRange{}); + + // Set the data clause operands + serialOp->getDataClauseOperandsMutable().append(copyinOp->getAccVar()); + + // Create dominance info + DominanceInfo domInfo(funcOp.get()); + PostDominanceInfo postDomInfo(funcOp.get()); + + // Get dominating data clauses + auto dataClauses = + getDominatingDataClauses(serialOp.get(), domInfo, postDomInfo); + + // Should contain the copyin from the serial op + EXPECT_EQ(dataClauses.size(), 1ul); + EXPECT_EQ(dataClauses[0], copyinOp->getAccVar()); +} + +TEST_F(OpenACCUtilsTest, getDominatingDataClausesEmpty) { + // Test with no data clauses at all + OwningOpRef module = ModuleOp::create(loc); + Block *moduleBlock = module->getBody(); + + OpBuilder::InsertionGuard guard(b); + b.setInsertionPointToStart(moduleBlock); + + // Create a function + auto funcType = b.getFunctionType({}, {}); + OwningOpRef funcOp = + func::FuncOp::create(b, loc, "test_func", funcType); + Block *funcBlock = funcOp->addEntryBlock(); + + b.setInsertionPointToStart(funcBlock); + + // Create a parallel op with no data clauses + OwningOpRef parallelOp = + ParallelOp::create(b, loc, TypeRange{}, ValueRange{}); + + // Create dominance info + DominanceInfo domInfo(funcOp.get()); + PostDominanceInfo postDomInfo(funcOp.get()); + + // Get dominating data clauses + auto dataClauses = + getDominatingDataClauses(parallelOp.get(), domInfo, postDomInfo); + + // Should be empty + EXPECT_EQ(dataClauses.size(), 0ul); +}