Skip to content

Commit 4c6a38c

Browse files
[acc] Expand OpenACCSupport to provide getRecipeName and emitNYI (llvm#165628)
Extends OpenACCSupport utilities to include recipe name generation and error reporting for unsupported features, providing foundation for variable privatization handling. Changes: - Add RecipeKind enum (private, firstprivate, reduction) for APIs that request a specific kind of recipe - Add getRecipeName() API to OpenACCSupport and OpenACCUtils that generates recipe names from types (e.g., "privatization_memref_5x10xf32_") - Add emitNYI() API to OpenACCSupport for graceful handling of not-yet-implemented cases - Generalize MemRefPointerLikeModel template to support UnrankedMemRefType - Add unit tests and integration tests for new APIs
1 parent 5430d7a commit 4c6a38c

File tree

10 files changed

+329
-5
lines changed

10 files changed

+329
-5
lines changed

mlir/include/mlir/Dialect/OpenACC/Analysis/OpenACCSupport.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@
5858
namespace mlir {
5959
namespace acc {
6060

61+
// Forward declaration for RecipeKind enum
62+
enum class RecipeKind : uint32_t;
63+
6164
namespace detail {
6265
/// This class contains internal trait classes used by OpenACCSupport.
6366
/// It follows the Concept-Model pattern used throughout MLIR (e.g., in
@@ -69,6 +72,13 @@ struct OpenACCSupportTraits {
6972

7073
/// Get the variable name for a given MLIR value.
7174
virtual std::string getVariableName(Value v) = 0;
75+
76+
/// Get the recipe name for a given kind, type and value.
77+
virtual std::string getRecipeName(RecipeKind kind, Type type,
78+
Value var) = 0;
79+
80+
// Used to report a case that is not supported by the implementation.
81+
virtual InFlightDiagnostic emitNYI(Location loc, const Twine &message) = 0;
7282
};
7383

7484
/// This class wraps a concrete OpenACCSupport implementation and forwards
@@ -84,6 +94,14 @@ struct OpenACCSupportTraits {
8494
return impl.getVariableName(v);
8595
}
8696

97+
std::string getRecipeName(RecipeKind kind, Type type, Value var) final {
98+
return impl.getRecipeName(kind, type, var);
99+
}
100+
101+
InFlightDiagnostic emitNYI(Location loc, const Twine &message) final {
102+
return impl.emitNYI(loc, message);
103+
}
104+
87105
private:
88106
ImplT impl;
89107
};
@@ -118,6 +136,24 @@ class OpenACCSupport {
118136
/// \return The variable name, or an empty string if unavailable.
119137
std::string getVariableName(Value v);
120138

139+
/// Get the recipe name for a given type and value.
140+
///
141+
/// \param kind The kind of recipe to get the name for.
142+
/// \param type The type to get the recipe name for. Can be null if the
143+
/// var is provided instead.
144+
/// \param var The MLIR value to get the recipe name for. Can be null if
145+
/// the type is provided instead.
146+
/// \return The recipe name, or an empty string if not available.
147+
std::string getRecipeName(RecipeKind kind, Type type, Value var);
148+
149+
/// Report a case that is not yet supported by the implementation.
150+
///
151+
/// \param loc The location to report the unsupported case at.
152+
/// \param message The message to report.
153+
/// \return An in-flight diagnostic object that can be used to report the
154+
/// unsupported case.
155+
InFlightDiagnostic emitNYI(Location loc, const Twine &message);
156+
121157
/// Signal that this analysis should always be preserved so that
122158
/// underlying implementation registration is not lost.
123159
bool isInvalidated(const AnalysisManager::PreservedAnalyses &pa) {

mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,26 @@ def OpenACC_LoopParMode : I32EnumAttr<
152152
let genSpecializedAttr = 0;
153153
}
154154

155+
def OpenACC_PrivateRecipe : I32EnumAttrCase<"private_recipe", 0>;
156+
def OpenACC_FirstprivateRecipe : I32EnumAttrCase<"firstprivate_recipe", 1>;
157+
def OpenACC_ReductionRecipe : I32EnumAttrCase<"reduction_recipe", 2>;
158+
159+
def OpenACC_RecipeKind : I32EnumAttr<
160+
"RecipeKind",
161+
"Encodes the options for kinds of recipes availabie in acc dialect",
162+
[
163+
OpenACC_PrivateRecipe, OpenACC_FirstprivateRecipe,
164+
OpenACC_ReductionRecipe]> {
165+
let cppNamespace = "::mlir::acc";
166+
let genSpecializedAttr = 0;
167+
}
168+
169+
def OpenACC_RecipeKindAttr : EnumAttr<OpenACC_Dialect,
170+
OpenACC_RecipeKind,
171+
"recipe_kind"> {
172+
let assemblyFormat = [{ ```<` $value `>` }];
173+
}
174+
155175
// Type used in operation below.
156176
def IntOrIndex : AnyTypeOf<[AnyInteger, Index]>;
157177

mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ mlir::acc::VariableTypeCategory getTypeCategory(mlir::Value var);
4343
/// empty string if no name is found.
4444
std::string getVariableName(mlir::Value v);
4545

46+
/// Get the recipe name for a given recipe kind and type.
47+
/// Returns an empty string if not possible to generate a recipe name.
48+
std::string getRecipeName(mlir::acc::RecipeKind kind, mlir::Type type);
49+
4650
} // namespace acc
4751
} // namespace mlir
4852

mlir/lib/Dialect/OpenACC/Analysis/OpenACCSupport.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,24 @@ std::string OpenACCSupport::getVariableName(Value v) {
2222
return acc::getVariableName(v);
2323
}
2424

25+
std::string OpenACCSupport::getRecipeName(RecipeKind kind, Type type,
26+
Value var) {
27+
if (impl)
28+
return impl->getRecipeName(kind, type, var);
29+
// The default implementation assumes that only type matters
30+
// and the actual instance of variable is not relevant.
31+
auto recipeName = acc::getRecipeName(kind, type);
32+
if (recipeName.empty())
33+
emitNYI(var ? var.getLoc() : UnknownLoc::get(type.getContext()),
34+
"variable privatization (incomplete recipe name handling)");
35+
return recipeName;
36+
}
37+
38+
InFlightDiagnostic OpenACCSupport::emitNYI(Location loc, const Twine &message) {
39+
if (impl)
40+
return impl->emitNYI(loc, message);
41+
return mlir::emitError(loc, "not yet implemented: " + message);
42+
}
43+
2544
} // namespace acc
2645
} // namespace mlir

mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@ static void attachVarNameAttr(Operation *op, OpBuilder &builder,
5050
}
5151
}
5252

53+
template <typename T>
5354
struct MemRefPointerLikeModel
54-
: public PointerLikeType::ExternalModel<MemRefPointerLikeModel,
55-
MemRefType> {
55+
: public PointerLikeType::ExternalModel<MemRefPointerLikeModel<T>, T> {
5656
Type getElementType(Type pointer) const {
57-
return cast<MemRefType>(pointer).getElementType();
57+
return cast<T>(pointer).getElementType();
5858
}
5959

6060
mlir::acc::VariableTypeCategory
@@ -63,7 +63,7 @@ struct MemRefPointerLikeModel
6363
if (auto mappableTy = dyn_cast<MappableType>(varType)) {
6464
return mappableTy.getTypeCategory(varPtr);
6565
}
66-
auto memrefTy = cast<MemRefType>(pointer);
66+
auto memrefTy = cast<T>(pointer);
6767
if (!memrefTy.hasRank()) {
6868
// This memref is unranked - aka it could have any rank, including a
6969
// rank of 0 which could mean scalar. For now, return uncategorized.
@@ -296,7 +296,10 @@ void OpenACCDialect::initialize() {
296296
// By attaching interfaces here, we make the OpenACC dialect dependent on
297297
// the other dialects. This is probably better than having dialects like LLVM
298298
// and memref be dependent on OpenACC.
299-
MemRefType::attachInterface<MemRefPointerLikeModel>(*getContext());
299+
MemRefType::attachInterface<MemRefPointerLikeModel<MemRefType>>(
300+
*getContext());
301+
UnrankedMemRefType::attachInterface<
302+
MemRefPointerLikeModel<UnrankedMemRefType>>(*getContext());
300303
LLVM::LLVMPointerType::attachInterface<LLVMPointerPointerLikeModel>(
301304
*getContext());
302305
}

mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "mlir/Dialect/OpenACC/OpenACC.h"
1212
#include "mlir/Interfaces/ViewLikeInterface.h"
1313
#include "llvm/ADT/TypeSwitch.h"
14+
#include "llvm/Support/Casting.h"
1415

1516
mlir::Operation *mlir::acc::getEnclosingComputeOp(mlir::Region &region) {
1617
mlir::Operation *parentOp = region.getParentOp();
@@ -106,3 +107,41 @@ std::string mlir::acc::getVariableName(mlir::Value v) {
106107

107108
return "";
108109
}
110+
111+
std::string mlir::acc::getRecipeName(mlir::acc::RecipeKind kind,
112+
mlir::Type type) {
113+
assert(kind == mlir::acc::RecipeKind::private_recipe ||
114+
kind == mlir::acc::RecipeKind::firstprivate_recipe ||
115+
kind == mlir::acc::RecipeKind::reduction_recipe);
116+
if (!llvm::isa<mlir::acc::PointerLikeType, mlir::acc::MappableType>(type))
117+
return "";
118+
119+
std::string recipeName;
120+
llvm::raw_string_ostream ss(recipeName);
121+
ss << (kind == mlir::acc::RecipeKind::private_recipe ? "privatization_"
122+
: kind == mlir::acc::RecipeKind::firstprivate_recipe
123+
? "firstprivatization_"
124+
: "reduction_");
125+
126+
// Print the type using its dialect-defined textual format.
127+
type.print(ss);
128+
ss.flush();
129+
130+
// Replace invalid characters (anything that's not a letter, number, or
131+
// period) since this needs to be a valid MLIR identifier.
132+
for (char &c : recipeName) {
133+
if (!std::isalnum(static_cast<unsigned char>(c)) && c != '.' && c != '_') {
134+
if (c == '?')
135+
c = 'U';
136+
else if (c == '*')
137+
c = 'Z';
138+
else if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' ||
139+
c == '}' || c == '<' || c == '>')
140+
c = '_';
141+
else
142+
c = 'X';
143+
}
144+
}
145+
146+
return recipeName;
147+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// RUN: mlir-opt %s -split-input-file -test-acc-support | FileCheck %s
2+
3+
// Test private recipe with 2D memref
4+
func.func @test_private_2d_memref() {
5+
// Create a 2D memref
6+
%0 = memref.alloca() {test.recipe_name = #acc.recipe_kind<private_recipe>} : memref<5x10xf32>
7+
8+
// CHECK: op=%{{.*}} = memref.alloca() {test.recipe_name = #acc.recipe_kind<private_recipe>} : memref<5x10xf32>
9+
// CHECK-NEXT: getRecipeName(kind=private_recipe, type=memref<5x10xf32>)="privatization_memref_5x10xf32_"
10+
11+
return
12+
}
13+
14+
// -----
15+
16+
// Test firstprivate recipe with 2D memref
17+
func.func @test_firstprivate_2d_memref() {
18+
// Create a 2D memref
19+
%0 = memref.alloca() {test.recipe_name = #acc.recipe_kind<firstprivate_recipe>} : memref<8x16xf64>
20+
21+
// CHECK: op=%{{.*}} = memref.alloca() {test.recipe_name = #acc.recipe_kind<firstprivate_recipe>} : memref<8x16xf64>
22+
// CHECK-NEXT: getRecipeName(kind=firstprivate_recipe, type=memref<8x16xf64>)="firstprivatization_memref_8x16xf64_"
23+
24+
return
25+
}
26+
27+
// -----
28+
29+
// Test reduction recipe with 2D memref
30+
func.func @test_reduction_2d_memref() {
31+
// Create a 2D memref
32+
%0 = memref.alloca() {test.recipe_name = #acc.recipe_kind<reduction_recipe>} : memref<4x8xi32>
33+
34+
// CHECK: op=%{{.*}} = memref.alloca() {test.recipe_name = #acc.recipe_kind<reduction_recipe>} : memref<4x8xi32>
35+
// CHECK-NEXT: getRecipeName(kind=reduction_recipe, type=memref<4x8xi32>)="reduction_memref_4x8xi32_"
36+
37+
return
38+
}
39+
40+
// -----
41+
42+
// Test private recipe with dynamic memref
43+
func.func @test_private_dynamic_memref(%arg0: memref<5x10xi32>) {
44+
// Cast to dynamic dimensions
45+
%0 = memref.cast %arg0 {test.recipe_name = #acc.recipe_kind<private_recipe>} : memref<5x10xi32> to memref<?x10xi32>
46+
47+
// CHECK: op=%{{.*}} = memref.cast %{{.*}} {test.recipe_name = #acc.recipe_kind<private_recipe>} : memref<5x10xi32> to memref<?x10xi32>
48+
// CHECK-NEXT: getRecipeName(kind=private_recipe, type=memref<?x10xi32>)="privatization_memref_Ux10xi32_"
49+
50+
return
51+
}
52+
53+
// -----
54+
55+
// Test private recipe with scalar memref
56+
func.func @test_private_scalar_memref() {
57+
// Create a scalar memref (no dimensions)
58+
%0 = memref.alloca() {test.recipe_name = #acc.recipe_kind<private_recipe>} : memref<i32>
59+
60+
// CHECK: op=%{{.*}} = memref.alloca() {test.recipe_name = #acc.recipe_kind<private_recipe>} : memref<i32>
61+
// CHECK-NEXT: getRecipeName(kind=private_recipe, type=memref<i32>)="privatization_memref_i32_"
62+
63+
return
64+
}
65+
66+
// -----
67+
68+
// Test private recipe with unranked memref
69+
func.func @test_private_unranked_memref(%arg0: memref<10xi32>) {
70+
// Cast to unranked memref
71+
%0 = memref.cast %arg0 {test.recipe_name = #acc.recipe_kind<private_recipe>} : memref<10xi32> to memref<*xi32>
72+
73+
// CHECK: op=%{{.*}} = memref.cast %{{.*}} {test.recipe_name = #acc.recipe_kind<private_recipe>} : memref<10xi32> to memref<*xi32>
74+
// CHECK-NEXT: getRecipeName(kind=private_recipe, type=memref<*xi32>)="privatization_memref_Zxi32_"
75+
76+
return
77+
}
78+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// RUN: mlir-opt %s --split-input-file -test-acc-support -verify-diagnostics
2+
3+
// Test emitNYI with a simple message
4+
func.func @test_emit_nyi() {
5+
// expected-error @below {{not yet implemented: Unsupported feature in OpenACC}}
6+
%0 = memref.alloca() {test.emit_nyi = "Unsupported feature in OpenACC"} : memref<10xi32>
7+
return
8+
}
9+
10+
// -----
11+
12+
// Test recipe name on load operation from scalar memref
13+
func.func @test_recipe_load_scalar() {
14+
%0 = memref.alloca() : memref<i32>
15+
// expected-error @below {{not yet implemented: variable privatization (incomplete recipe name handling)}}
16+
%1 = memref.load %0[] {test.recipe_name = #acc.recipe_kind<private_recipe>} : memref<i32>
17+
return
18+
}

mlir/test/lib/Dialect/OpenACC/TestOpenACCSupport.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,28 @@ void TestOpenACCSupportPass::runOnOperation() {
5757
<< "\"\n";
5858
}
5959
}
60+
61+
// Check for test.recipe_name attribute. This is the marker used to identify
62+
// the operations that need to be tested for getRecipeName.
63+
if (auto recipeAttr =
64+
op->getAttrOfType<RecipeKindAttr>("test.recipe_name")) {
65+
RecipeKind kind = recipeAttr.getValue();
66+
// Get the type from the first result if available
67+
if (op->getNumResults() > 0) {
68+
Type type = op->getResult(0).getType();
69+
std::string recipeName =
70+
support.getRecipeName(kind, type, op->getResult(0));
71+
llvm::outs() << "op=" << *op
72+
<< "\n\tgetRecipeName(kind=" << stringifyRecipeKind(kind)
73+
<< ", type=" << type << ")=\"" << recipeName << "\"\n";
74+
}
75+
}
76+
77+
// Check for test.emit_nyi attribute. This is the marker used to
78+
// test whether the not yet implemented case is reported correctly.
79+
if (auto messageAttr = op->getAttrOfType<StringAttr>("test.emit_nyi")) {
80+
support.emitNYI(op->getLoc(), messageAttr.getValue());
81+
}
6082
});
6183
}
6284

0 commit comments

Comments
 (0)