[mlir][acc] Add OpenACCSupport for extensible dialect handling (#164510)

The OpenACC dialect must coexist with source language dialects (FIR,
CIR, etc.) to enable offloading. While type interfaces
(`PointerLikeType` and `MappableType`) provide the primary contract for
variable mapping, some scenarios require pipeline-specific customization
or need to express information that cannot be adequately captured
through operation and type interfaces alone.

This commit introduces the `OpenACCSupport` analysis, which provides
extensible support APIs that can be customized per-pipeline. The
analysis follows the Concept-Model pattern used in MLIR's
`AliasAnalysis` and is never invalidated, persisting throughout the pass
pipeline.

The initial API, `getVariableName(Value) -> string`, retrieves variable
names from MLIR values by:
- Checking for `acc.var_name` attributes
- Extracting names from ACC data clause operations (e.g., `acc.copyin`)
- Walking through `ViewLikeOpInterface` operations to find the source

This will be used in the implicit data mapping pass to automatically
generate device mappings with correct user-visible variable names.

Usage: Passes call `getAnalysis<OpenACCSupport>()` to get a cached
instance with either the default or a previously- registered custom
implementation. Custom implementations can be registered in a setup pass
by calling `setImplementation()` before the consumer pass runs.
This commit is contained in:
Razvan Lupusoru
2025-10-22 08:26:21 -07:00
committed by GitHub
parent 003101e5f8
commit f3599e55d1
11 changed files with 448 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
//===- OpenACCSupport.h - OpenACC Support Interface -------------*- 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
//
//===----------------------------------------------------------------------===//
//
// This file defines the OpenACCSupport analysis interface, which provides
// extensible support for OpenACC passes. Custom implementations
// can be registered to provide pipeline and dialect-specific information
// that cannot be adequately expressed through type or operation interfaces
// alone.
//
// Usage Pattern:
// ==============
//
// A pass that needs this functionality should call
// getAnalysis<OpenACCSupport>(), which will provide either:
// - A cached version if previously initialized, OR
// - A default implementation if not previously initialized
//
// This analysis is never invalidated (isInvalidated returns false), so it only
// needs to be initialized once and will persist throughout the pass pipeline.
//
// Registering a Custom Implementation:
// =====================================
//
// If a custom implementation is needed, create a pass that runs BEFORE the pass
// that needs the analysis. In this setup pass, use
// getAnalysis<OpenACCSupport>() followed by setImplementation() to register
// your custom implementation. The custom implementation will need to provide
// implementation for all methods defined in the `OpenACCSupportTraits::Concept`
// class.
//
// Example:
// void MySetupPass::runOnOperation() {
// OpenACCSupport &support = getAnalysis<OpenACCSupport>();
// support.setImplementation(MyCustomImpl());
// }
//
// void MyAnalysisConsumerPass::runOnOperation() {
// OpenACCSupport &support = getAnalysis<OpenACCSupport>();
// std::string name = support.getVariableName(someValue);
// // ... use the analysis results
// }
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_DIALECT_OPENACC_ANALYSIS_OPENACCSUPPORT_H
#define MLIR_DIALECT_OPENACC_ANALYSIS_OPENACCSUPPORT_H
#include "mlir/IR/Value.h"
#include "mlir/Pass/AnalysisManager.h"
#include <memory>
#include <string>
namespace mlir {
namespace acc {
namespace detail {
/// This class contains internal trait classes used by OpenACCSupport.
/// It follows the Concept-Model pattern used throughout MLIR (e.g., in
/// AliasAnalysis and interface definitions).
struct OpenACCSupportTraits {
class Concept {
public:
virtual ~Concept() = default;
/// Get the variable name for a given MLIR value.
virtual std::string getVariableName(Value v) = 0;
};
/// This class wraps a concrete OpenACCSupport implementation and forwards
/// interface calls to it. This provides type erasure, allowing different
/// implementation types to be used interchangeably without inheritance.
template <typename ImplT>
class Model final : public Concept {
public:
explicit Model(ImplT &&impl) : impl(std::forward<ImplT>(impl)) {}
~Model() override = default;
std::string getVariableName(Value v) final {
return impl.getVariableName(v);
}
private:
ImplT impl;
};
};
} // namespace detail
//===----------------------------------------------------------------------===//
// OpenACCSupport
//===----------------------------------------------------------------------===//
class OpenACCSupport {
using Concept = detail::OpenACCSupportTraits::Concept;
template <typename ImplT>
using Model = detail::OpenACCSupportTraits::Model<ImplT>;
public:
OpenACCSupport() = default;
OpenACCSupport(Operation *op) {}
/// Register a custom OpenACCSupport implementation. Only one implementation
/// can be registered at a time; calling this replaces any existing
/// implementation.
template <typename AnalysisT>
void setImplementation(AnalysisT &&analysis) {
impl =
std::make_unique<Model<AnalysisT>>(std::forward<AnalysisT>(analysis));
}
/// Get the variable name for a given value.
///
/// \param v The MLIR value to get the variable name for.
/// \return The variable name, or an empty string if unavailable.
std::string getVariableName(Value v);
/// Signal that this analysis should always be preserved so that
/// underlying implementation registration is not lost.
bool isInvalidated(const AnalysisManager::PreservedAnalyses &pa) {
return false;
}
private:
/// The registered custom implementation (if any).
std::unique_ptr<Concept> impl;
};
} // namespace acc
} // namespace mlir
#endif // MLIR_DIALECT_OPENACC_ANALYSIS_OPENACCSUPPORT_H

View File

@@ -38,6 +38,11 @@ std::optional<ClauseDefaultValue> getDefaultAttr(mlir::Operation *op);
/// Get the type category of an OpenACC variable.
mlir::acc::VariableTypeCategory getTypeCategory(mlir::Value var);
/// Attempts to extract the variable name from a value by walking through
/// view-like operations until an `acc.var_name` attribute is found. Returns
/// empty string if no name is found.
std::string getVariableName(mlir::Value v);
} // namespace acc
} // namespace mlir

View File

@@ -0,0 +1,13 @@
add_mlir_dialect_library(MLIROpenACCAnalysis
OpenACCSupport.cpp
ADDITIONAL_HEADER_DIRS
${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/OpenACC
LINK_LIBS PUBLIC
MLIRIR
MLIROpenACCDialect
MLIROpenACCUtils
MLIRSupport
)

View File

@@ -0,0 +1,26 @@
//===- OpenACCSupport.cpp - OpenACCSupport Implementation -----------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file implements the OpenACCSupport analysis interface.
//
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/OpenACC/Analysis/OpenACCSupport.h"
#include "mlir/Dialect/OpenACC/OpenACCUtils.h"
namespace mlir {
namespace acc {
std::string OpenACCSupport::getVariableName(Value v) {
if (impl)
return impl->getVariableName(v);
return acc::getVariableName(v);
}
} // namespace acc
} // namespace mlir

View File

@@ -1,3 +1,4 @@
add_subdirectory(Analysis)
add_subdirectory(IR)
add_subdirectory(Utils)
add_subdirectory(Transforms)

View File

@@ -9,6 +9,7 @@
#include "mlir/Dialect/OpenACC/OpenACCUtils.h"
#include "mlir/Dialect/OpenACC/OpenACC.h"
#include "mlir/Interfaces/ViewLikeInterface.h"
#include "llvm/ADT/TypeSwitch.h"
mlir::Operation *mlir::acc::getEnclosingComputeOp(mlir::Region &region) {
@@ -78,3 +79,30 @@ mlir::acc::VariableTypeCategory mlir::acc::getTypeCategory(mlir::Value var) {
pointerLikeTy.getElementType());
return typeCategory;
}
std::string mlir::acc::getVariableName(mlir::Value v) {
Value current = v;
// Walk through view operations until a name is found or can't go further
while (Operation *definingOp = current.getDefiningOp()) {
// Check for `acc.var_name` attribute
if (auto varNameAttr =
definingOp->getAttrOfType<VarNameAttr>(getVarNameAttrName()))
return varNameAttr.getName().str();
// If it is a data entry operation, get name via getVarName
if (isa<ACC_DATA_ENTRY_OPS>(definingOp))
if (auto name = acc::getVarName(definingOp))
return name->str();
// If it's a view operation, continue to the source
if (auto viewOp = dyn_cast<ViewLikeOpInterface>(definingOp)) {
current = viewOp.getViewSource();
continue;
}
break;
}
return "";
}

View File

@@ -0,0 +1,88 @@
// RUN: mlir-opt %s -split-input-file -test-acc-support | FileCheck %s
// Test with direct variable names
func.func @test_direct_var_name() {
// Create a memref with acc.var_name attribute
%0 = memref.alloca() {acc.var_name = #acc.var_name<"my_variable">} : memref<10xi32>
%1 = memref.cast %0 {test.var_name} : memref<10xi32> to memref<10xi32>
// CHECK: op=%{{.*}} = memref.cast %{{.*}} {test.var_name} : memref<10xi32> to memref<10xi32>
// CHECK-NEXT: getVariableName="my_variable"
return
}
// -----
// Test through memref.cast
func.func @test_through_cast() {
// Create a 5x2 memref with acc.var_name attribute
%0 = memref.alloca() {acc.var_name = #acc.var_name<"casted_variable">} : memref<5x2xi32>
// Cast to dynamic dimensions
%1 = memref.cast %0 : memref<5x2xi32> to memref<?x?xi32>
// Mark with test attribute - should find name through cast
%2 = memref.cast %1 {test.var_name} : memref<?x?xi32> to memref<5x2xi32>
// CHECK: op=%{{.*}} = memref.cast %{{.*}} {test.var_name} : memref<?x?xi32> to memref<5x2xi32>
// CHECK-NEXT: getVariableName="casted_variable"
return
}
// -----
// Test with no variable name
func.func @test_no_var_name() {
// Create a memref without acc.var_name attribute
%0 = memref.alloca() : memref<10xi32>
// Mark with test attribute - should find empty string
%1 = memref.cast %0 {test.var_name} : memref<10xi32> to memref<10xi32>
// CHECK: op=%{{.*}} = memref.cast %{{.*}} {test.var_name} : memref<10xi32> to memref<10xi32>
// CHECK-NEXT: getVariableName=""
return
}
// -----
// Test through multiple casts
func.func @test_multiple_casts() {
// Create a memref with acc.var_name attribute
%0 = memref.alloca() {acc.var_name = #acc.var_name<"multi_cast">} : memref<10xi32>
// Multiple casts
%1 = memref.cast %0 : memref<10xi32> to memref<?xi32>
%2 = memref.cast %1 : memref<?xi32> to memref<10xi32>
// Mark with test attribute - should find name through multiple casts
%3 = memref.cast %2 {test.var_name} : memref<10xi32> to memref<10xi32>
// CHECK: op=%{{.*}} = memref.cast %{{.*}} {test.var_name} : memref<10xi32> to memref<10xi32>
// CHECK-NEXT: getVariableName="multi_cast"
return
}
// -----
// Test with acc.copyin operation
func.func @test_copyin_name() {
// Create a memref
%0 = memref.alloca() : memref<10xf32>
// Create an acc.copyin operation with a name
%1 = acc.copyin varPtr(%0 : memref<10xf32>) -> memref<10xf32> {name = "input_data"}
// Mark with test attribute - should find name from copyin operation
%2 = memref.cast %1 {test.var_name} : memref<10xf32> to memref<?xf32>
// CHECK: op=%{{.*}} = memref.cast %{{.*}} {test.var_name} : memref<10xf32> to memref<?xf32>
// CHECK-NEXT: getVariableName="input_data"
return
}

View File

@@ -2,6 +2,7 @@ add_mlir_library(MLIROpenACCTestPasses
TestOpenACC.cpp
TestPointerLikeTypeInterface.cpp
TestRecipePopulate.cpp
TestOpenACCSupport.cpp
EXCLUDE_FROM_LIBMLIR
)
@@ -11,6 +12,7 @@ mlir_target_link_libraries(MLIROpenACCTestPasses PUBLIC
MLIRFuncDialect
MLIRMemRefDialect
MLIROpenACCDialect
MLIROpenACCAnalysis
MLIRPass
MLIRSupport
)

View File

@@ -16,11 +16,13 @@ namespace test {
// Forward declarations of individual test pass registration functions
void registerTestPointerLikeTypeInterfacePass();
void registerTestRecipePopulatePass();
void registerTestOpenACCSupportPass();
// Unified registration function for all OpenACC tests
void registerTestOpenACC() {
registerTestPointerLikeTypeInterfacePass();
registerTestRecipePopulatePass();
registerTestOpenACCSupportPass();
}
} // namespace test

View File

@@ -0,0 +1,73 @@
//===- TestOpenACCSupport.cpp - Test OpenACCSupport Analysis -------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file contains test passes for testing the OpenACCSupport analysis.
//
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/OpenACC/Analysis/OpenACCSupport.h"
#include "mlir/Dialect/OpenACC/OpenACC.h"
#include "mlir/Pass/Pass.h"
using namespace mlir;
using namespace mlir::acc;
namespace {
struct TestOpenACCSupportPass
: public PassWrapper<TestOpenACCSupportPass, OperationPass<func::FuncOp>> {
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(TestOpenACCSupportPass)
StringRef getArgument() const override { return "test-acc-support"; }
StringRef getDescription() const override {
return "Test OpenACCSupport analysis";
}
void runOnOperation() override;
void getDependentDialects(DialectRegistry &registry) const override {
registry.insert<acc::OpenACCDialect>();
registry.insert<memref::MemRefDialect>();
}
};
void TestOpenACCSupportPass::runOnOperation() {
auto func = getOperation();
// Get the OpenACCSupport analysis
OpenACCSupport &support = getAnalysis<OpenACCSupport>();
// Walk through operations looking for test attributes
func.walk([&](Operation *op) {
// Check for test.var_name attribute. This is the marker used to identify
// the operations that need to be tested for getVariableName.
if (op->hasAttr("test.var_name")) {
// For each result of this operation, try to get the variable name
for (auto result : op->getResults()) {
std::string foundName = support.getVariableName(result);
llvm::outs() << "op=" << *op << "\n\tgetVariableName=\"" << foundName
<< "\"\n";
}
}
});
}
} // namespace
namespace mlir {
namespace test {
void registerTestOpenACCSupportPass() {
PassRegistration<TestOpenACCSupportPass>();
}
} // namespace test
} // namespace mlir

View File

@@ -410,3 +410,78 @@ TEST_F(OpenACCUtilsTest, getTypeCategoryArray) {
VariableTypeCategory category = getTypeCategory(varPtr);
EXPECT_EQ(category, VariableTypeCategory::array);
}
//===----------------------------------------------------------------------===//
// getVariableName Tests
//===----------------------------------------------------------------------===//
TEST_F(OpenACCUtilsTest, getVariableNameDirect) {
// Create a memref with acc.var_name attribute
auto memrefTy = MemRefType::get({10}, b.getI32Type());
OwningOpRef<memref::AllocaOp> allocOp =
memref::AllocaOp::create(b, loc, memrefTy);
// Set the acc.var_name attribute
auto varNameAttr = VarNameAttr::get(&context, "my_variable");
allocOp.get()->setAttr(getVarNameAttrName(), varNameAttr);
Value varPtr = allocOp->getResult();
// Test that getVariableName returns the variable name
std::string varName = getVariableName(varPtr);
EXPECT_EQ(varName, "my_variable");
}
TEST_F(OpenACCUtilsTest, getVariableNameThroughCast) {
// Create a 5x2 memref with acc.var_name attribute
auto memrefTy = MemRefType::get({5, 2}, b.getI32Type());
OwningOpRef<memref::AllocaOp> allocOp =
memref::AllocaOp::create(b, loc, memrefTy);
// Set the acc.var_name attribute on the alloca
auto varNameAttr = VarNameAttr::get(&context, "casted_variable");
allocOp.get()->setAttr(getVarNameAttrName(), varNameAttr);
Value allocResult = allocOp->getResult();
// Create a memref.cast operation to a flattened 10-element array
auto castedMemrefTy = MemRefType::get({10}, b.getI32Type());
OwningOpRef<memref::CastOp> castOp =
memref::CastOp::create(b, loc, castedMemrefTy, allocResult);
Value castedPtr = castOp->getResult();
// Test that getVariableName walks through the cast to find the variable name
std::string varName = getVariableName(castedPtr);
EXPECT_EQ(varName, "casted_variable");
}
TEST_F(OpenACCUtilsTest, getVariableNameNotFound) {
// Create a memref without acc.var_name attribute
auto memrefTy = MemRefType::get({10}, b.getI32Type());
OwningOpRef<memref::AllocaOp> allocOp =
memref::AllocaOp::create(b, loc, memrefTy);
Value varPtr = allocOp->getResult();
// Test that getVariableName returns empty string when no name is found
std::string varName = getVariableName(varPtr);
EXPECT_EQ(varName, "");
}
TEST_F(OpenACCUtilsTest, getVariableNameFromCopyin) {
// Create a memref
auto memrefTy = MemRefType::get({10}, b.getI32Type());
OwningOpRef<memref::AllocaOp> allocOp =
memref::AllocaOp::create(b, loc, memrefTy);
Value varPtr = allocOp->getResult();
StringRef name = "data_array";
OwningOpRef<CopyinOp> copyinOp =
CopyinOp::create(b, loc, varPtr, /*structured=*/true, /*implicit=*/true,
/*name=*/name);
// Test that getVariableName extracts the name from the copyin operation
std::string varName = getVariableName(copyinOp->getAccVar());
EXPECT_EQ(varName, name);
}