diff --git a/mlir/include/mlir/Dialect/OpenACC/Analysis/OpenACCSupport.h b/mlir/include/mlir/Dialect/OpenACC/Analysis/OpenACCSupport.h index 0833462ea0509..d9b2646b753f3 100644 --- a/mlir/include/mlir/Dialect/OpenACC/Analysis/OpenACCSupport.h +++ b/mlir/include/mlir/Dialect/OpenACC/Analysis/OpenACCSupport.h @@ -58,6 +58,9 @@ namespace mlir { namespace acc { +// Forward declaration for RecipeKind enum +enum class RecipeKind : uint32_t; + namespace detail { /// This class contains internal trait classes used by OpenACCSupport. /// It follows the Concept-Model pattern used throughout MLIR (e.g., in @@ -69,6 +72,13 @@ struct OpenACCSupportTraits { /// Get the variable name for a given MLIR value. virtual std::string getVariableName(Value v) = 0; + + /// Get the recipe name for a given kind, type and value. + virtual std::string getRecipeName(RecipeKind kind, Type type, + Value var) = 0; + + // Used to report a case that is not supported by the implementation. + virtual InFlightDiagnostic emitNYI(Location loc, const Twine &message) = 0; }; /// This class wraps a concrete OpenACCSupport implementation and forwards @@ -84,6 +94,14 @@ struct OpenACCSupportTraits { return impl.getVariableName(v); } + std::string getRecipeName(RecipeKind kind, Type type, Value var) final { + return impl.getRecipeName(kind, type, var); + } + + InFlightDiagnostic emitNYI(Location loc, const Twine &message) final { + return impl.emitNYI(loc, message); + } + private: ImplT impl; }; @@ -118,6 +136,24 @@ class OpenACCSupport { /// \return The variable name, or an empty string if unavailable. std::string getVariableName(Value v); + /// Get the recipe name for a given type and value. + /// + /// \param kind The kind of recipe to get the name for. + /// \param type The type to get the recipe name for. Can be null if the + /// var is provided instead. + /// \param var The MLIR value to get the recipe name for. Can be null if + /// the type is provided instead. + /// \return The recipe name, or an empty string if not available. + std::string getRecipeName(RecipeKind kind, Type type, Value var); + + /// Report a case that is not yet supported by the implementation. + /// + /// \param loc The location to report the unsupported case at. + /// \param message The message to report. + /// \return An in-flight diagnostic object that can be used to report the + /// unsupported case. + InFlightDiagnostic emitNYI(Location loc, const Twine &message); + /// Signal that this analysis should always be preserved so that /// underlying implementation registration is not lost. bool isInvalidated(const AnalysisManager::PreservedAnalyses &pa) { diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td index a18c18af8a753..2f4517ddfe754 100644 --- a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td +++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td @@ -152,6 +152,26 @@ def OpenACC_LoopParMode : I32EnumAttr< let genSpecializedAttr = 0; } +def OpenACC_PrivateRecipe : I32EnumAttrCase<"private_recipe", 0>; +def OpenACC_FirstprivateRecipe : I32EnumAttrCase<"firstprivate_recipe", 1>; +def OpenACC_ReductionRecipe : I32EnumAttrCase<"reduction_recipe", 2>; + +def OpenACC_RecipeKind : I32EnumAttr< + "RecipeKind", + "Encodes the options for kinds of recipes availabie in acc dialect", + [ + OpenACC_PrivateRecipe, OpenACC_FirstprivateRecipe, + OpenACC_ReductionRecipe]> { + let cppNamespace = "::mlir::acc"; + let genSpecializedAttr = 0; +} + +def OpenACC_RecipeKindAttr : EnumAttr { + let assemblyFormat = [{ ```<` $value `>` }]; +} + // Type used in operation below. def IntOrIndex : AnyTypeOf<[AnyInteger, Index]>; diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h index 0ee88c6f47b67..563c1e0099fc0 100644 --- a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h +++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h @@ -43,6 +43,10 @@ mlir::acc::VariableTypeCategory getTypeCategory(mlir::Value var); /// empty string if no name is found. std::string getVariableName(mlir::Value v); +/// Get the recipe name for a given recipe kind and type. +/// Returns an empty string if not possible to generate a recipe name. +std::string getRecipeName(mlir::acc::RecipeKind kind, mlir::Type type); + } // namespace acc } // namespace mlir diff --git a/mlir/lib/Dialect/OpenACC/Analysis/OpenACCSupport.cpp b/mlir/lib/Dialect/OpenACC/Analysis/OpenACCSupport.cpp index f6b4534794eaf..40e769e7068cf 100644 --- a/mlir/lib/Dialect/OpenACC/Analysis/OpenACCSupport.cpp +++ b/mlir/lib/Dialect/OpenACC/Analysis/OpenACCSupport.cpp @@ -22,5 +22,24 @@ std::string OpenACCSupport::getVariableName(Value v) { return acc::getVariableName(v); } +std::string OpenACCSupport::getRecipeName(RecipeKind kind, Type type, + Value var) { + if (impl) + return impl->getRecipeName(kind, type, var); + // The default implementation assumes that only type matters + // and the actual instance of variable is not relevant. + auto recipeName = acc::getRecipeName(kind, type); + if (recipeName.empty()) + emitNYI(var ? var.getLoc() : UnknownLoc::get(type.getContext()), + "variable privatization (incomplete recipe name handling)"); + return recipeName; +} + +InFlightDiagnostic OpenACCSupport::emitNYI(Location loc, const Twine &message) { + if (impl) + return impl->emitNYI(loc, message); + return mlir::emitError(loc, "not yet implemented: " + message); +} + } // namespace acc } // namespace mlir diff --git a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp index ca46629919dba..35eba724a9059 100644 --- a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp +++ b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp @@ -50,11 +50,11 @@ static void attachVarNameAttr(Operation *op, OpBuilder &builder, } } +template struct MemRefPointerLikeModel - : public PointerLikeType::ExternalModel { + : public PointerLikeType::ExternalModel, T> { Type getElementType(Type pointer) const { - return cast(pointer).getElementType(); + return cast(pointer).getElementType(); } mlir::acc::VariableTypeCategory @@ -63,7 +63,7 @@ struct MemRefPointerLikeModel if (auto mappableTy = dyn_cast(varType)) { return mappableTy.getTypeCategory(varPtr); } - auto memrefTy = cast(pointer); + auto memrefTy = cast(pointer); if (!memrefTy.hasRank()) { // This memref is unranked - aka it could have any rank, including a // rank of 0 which could mean scalar. For now, return uncategorized. @@ -296,7 +296,10 @@ void OpenACCDialect::initialize() { // By attaching interfaces here, we make the OpenACC dialect dependent on // the other dialects. This is probably better than having dialects like LLVM // and memref be dependent on OpenACC. - MemRefType::attachInterface(*getContext()); + MemRefType::attachInterface>( + *getContext()); + UnrankedMemRefType::attachInterface< + MemRefPointerLikeModel>(*getContext()); LLVM::LLVMPointerType::attachInterface( *getContext()); } diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp index 89adda82646e6..660c3138af0ec 100644 --- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp +++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp @@ -11,6 +11,7 @@ #include "mlir/Dialect/OpenACC/OpenACC.h" #include "mlir/Interfaces/ViewLikeInterface.h" #include "llvm/ADT/TypeSwitch.h" +#include "llvm/Support/Casting.h" mlir::Operation *mlir::acc::getEnclosingComputeOp(mlir::Region ®ion) { mlir::Operation *parentOp = region.getParentOp(); @@ -106,3 +107,41 @@ std::string mlir::acc::getVariableName(mlir::Value v) { return ""; } + +std::string mlir::acc::getRecipeName(mlir::acc::RecipeKind kind, + mlir::Type type) { + assert(kind == mlir::acc::RecipeKind::private_recipe || + kind == mlir::acc::RecipeKind::firstprivate_recipe || + kind == mlir::acc::RecipeKind::reduction_recipe); + if (!llvm::isa(type)) + return ""; + + std::string recipeName; + llvm::raw_string_ostream ss(recipeName); + ss << (kind == mlir::acc::RecipeKind::private_recipe ? "privatization_" + : kind == mlir::acc::RecipeKind::firstprivate_recipe + ? "firstprivatization_" + : "reduction_"); + + // Print the type using its dialect-defined textual format. + type.print(ss); + ss.flush(); + + // Replace invalid characters (anything that's not a letter, number, or + // period) since this needs to be a valid MLIR identifier. + for (char &c : recipeName) { + if (!std::isalnum(static_cast(c)) && c != '.' && c != '_') { + if (c == '?') + c = 'U'; + else if (c == '*') + c = 'Z'; + else if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || + c == '}' || c == '<' || c == '>') + c = '_'; + else + c = 'X'; + } + } + + return recipeName; +} diff --git a/mlir/test/Dialect/OpenACC/support-analysis-recipename.mlir b/mlir/test/Dialect/OpenACC/support-analysis-recipename.mlir new file mode 100644 index 0000000000000..8ea53b5d0f4d4 --- /dev/null +++ b/mlir/test/Dialect/OpenACC/support-analysis-recipename.mlir @@ -0,0 +1,78 @@ +// RUN: mlir-opt %s -split-input-file -test-acc-support | FileCheck %s + +// Test private recipe with 2D memref +func.func @test_private_2d_memref() { + // Create a 2D memref + %0 = memref.alloca() {test.recipe_name = #acc.recipe_kind} : memref<5x10xf32> + + // CHECK: op=%{{.*}} = memref.alloca() {test.recipe_name = #acc.recipe_kind} : memref<5x10xf32> + // CHECK-NEXT: getRecipeName(kind=private_recipe, type=memref<5x10xf32>)="privatization_memref_5x10xf32_" + + return +} + +// ----- + +// Test firstprivate recipe with 2D memref +func.func @test_firstprivate_2d_memref() { + // Create a 2D memref + %0 = memref.alloca() {test.recipe_name = #acc.recipe_kind} : memref<8x16xf64> + + // CHECK: op=%{{.*}} = memref.alloca() {test.recipe_name = #acc.recipe_kind} : memref<8x16xf64> + // CHECK-NEXT: getRecipeName(kind=firstprivate_recipe, type=memref<8x16xf64>)="firstprivatization_memref_8x16xf64_" + + return +} + +// ----- + +// Test reduction recipe with 2D memref +func.func @test_reduction_2d_memref() { + // Create a 2D memref + %0 = memref.alloca() {test.recipe_name = #acc.recipe_kind} : memref<4x8xi32> + + // CHECK: op=%{{.*}} = memref.alloca() {test.recipe_name = #acc.recipe_kind} : memref<4x8xi32> + // CHECK-NEXT: getRecipeName(kind=reduction_recipe, type=memref<4x8xi32>)="reduction_memref_4x8xi32_" + + return +} + +// ----- + +// Test private recipe with dynamic memref +func.func @test_private_dynamic_memref(%arg0: memref<5x10xi32>) { + // Cast to dynamic dimensions + %0 = memref.cast %arg0 {test.recipe_name = #acc.recipe_kind} : memref<5x10xi32> to memref + + // CHECK: op=%{{.*}} = memref.cast %{{.*}} {test.recipe_name = #acc.recipe_kind} : memref<5x10xi32> to memref + // CHECK-NEXT: getRecipeName(kind=private_recipe, type=memref)="privatization_memref_Ux10xi32_" + + return +} + +// ----- + +// Test private recipe with scalar memref +func.func @test_private_scalar_memref() { + // Create a scalar memref (no dimensions) + %0 = memref.alloca() {test.recipe_name = #acc.recipe_kind} : memref + + // CHECK: op=%{{.*}} = memref.alloca() {test.recipe_name = #acc.recipe_kind} : memref + // CHECK-NEXT: getRecipeName(kind=private_recipe, type=memref)="privatization_memref_i32_" + + return +} + +// ----- + +// Test private recipe with unranked memref +func.func @test_private_unranked_memref(%arg0: memref<10xi32>) { + // Cast to unranked memref + %0 = memref.cast %arg0 {test.recipe_name = #acc.recipe_kind} : memref<10xi32> to memref<*xi32> + + // CHECK: op=%{{.*}} = memref.cast %{{.*}} {test.recipe_name = #acc.recipe_kind} : memref<10xi32> to memref<*xi32> + // CHECK-NEXT: getRecipeName(kind=private_recipe, type=memref<*xi32>)="privatization_memref_Zxi32_" + + return +} + diff --git a/mlir/test/Dialect/OpenACC/support-analysis-unsupported.mlir b/mlir/test/Dialect/OpenACC/support-analysis-unsupported.mlir new file mode 100644 index 0000000000000..c4d5b81a1380a --- /dev/null +++ b/mlir/test/Dialect/OpenACC/support-analysis-unsupported.mlir @@ -0,0 +1,18 @@ +// RUN: mlir-opt %s --split-input-file -test-acc-support -verify-diagnostics + +// Test emitNYI with a simple message +func.func @test_emit_nyi() { + // expected-error @below {{not yet implemented: Unsupported feature in OpenACC}} + %0 = memref.alloca() {test.emit_nyi = "Unsupported feature in OpenACC"} : memref<10xi32> + return +} + +// ----- + +// Test recipe name on load operation from scalar memref +func.func @test_recipe_load_scalar() { + %0 = memref.alloca() : memref + // expected-error @below {{not yet implemented: variable privatization (incomplete recipe name handling)}} + %1 = memref.load %0[] {test.recipe_name = #acc.recipe_kind} : memref + return +} diff --git a/mlir/test/lib/Dialect/OpenACC/TestOpenACCSupport.cpp b/mlir/test/lib/Dialect/OpenACC/TestOpenACCSupport.cpp index 8bf984bdc2632..7c8b08489c62e 100644 --- a/mlir/test/lib/Dialect/OpenACC/TestOpenACCSupport.cpp +++ b/mlir/test/lib/Dialect/OpenACC/TestOpenACCSupport.cpp @@ -57,6 +57,28 @@ void TestOpenACCSupportPass::runOnOperation() { << "\"\n"; } } + + // Check for test.recipe_name attribute. This is the marker used to identify + // the operations that need to be tested for getRecipeName. + if (auto recipeAttr = + op->getAttrOfType("test.recipe_name")) { + RecipeKind kind = recipeAttr.getValue(); + // Get the type from the first result if available + if (op->getNumResults() > 0) { + Type type = op->getResult(0).getType(); + std::string recipeName = + support.getRecipeName(kind, type, op->getResult(0)); + llvm::outs() << "op=" << *op + << "\n\tgetRecipeName(kind=" << stringifyRecipeKind(kind) + << ", type=" << type << ")=\"" << recipeName << "\"\n"; + } + } + + // Check for test.emit_nyi attribute. This is the marker used to + // test whether the not yet implemented case is reported correctly. + if (auto messageAttr = op->getAttrOfType("test.emit_nyi")) { + support.emitNYI(op->getLoc(), messageAttr.getValue()); + } }); } diff --git a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp index 3fbbcc90a67c9..f1fe53c15a6f5 100644 --- a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp +++ b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp @@ -485,3 +485,88 @@ TEST_F(OpenACCUtilsTest, getVariableNameFromCopyin) { std::string varName = getVariableName(copyinOp->getAccVar()); EXPECT_EQ(varName, name); } + +//===----------------------------------------------------------------------===// +// getRecipeName Tests +//===----------------------------------------------------------------------===// + +TEST_F(OpenACCUtilsTest, getRecipeNamePrivateScalarMemref) { + // Create a scalar memref type + auto scalarMemrefTy = MemRefType::get({}, b.getI32Type()); + + // Test private recipe with scalar memref + std::string recipeName = + getRecipeName(RecipeKind::private_recipe, scalarMemrefTy); + EXPECT_EQ(recipeName, "privatization_memref_i32_"); +} + +TEST_F(OpenACCUtilsTest, getRecipeNameFirstprivateScalarMemref) { + // Create a scalar memref type + auto scalarMemrefTy = MemRefType::get({}, b.getF32Type()); + + // Test firstprivate recipe with scalar memref + std::string recipeName = + getRecipeName(RecipeKind::firstprivate_recipe, scalarMemrefTy); + EXPECT_EQ(recipeName, "firstprivatization_memref_f32_"); +} + +TEST_F(OpenACCUtilsTest, getRecipeNameReductionScalarMemref) { + // Create a scalar memref type + auto scalarMemrefTy = MemRefType::get({}, b.getI64Type()); + + // Test reduction recipe with scalar memref + std::string recipeName = + getRecipeName(RecipeKind::reduction_recipe, scalarMemrefTy); + EXPECT_EQ(recipeName, "reduction_memref_i64_"); +} + +TEST_F(OpenACCUtilsTest, getRecipeNamePrivate2DMemref) { + // Create a 2D memref type + auto memref2DTy = MemRefType::get({5, 10}, b.getF32Type()); + + // Test private recipe with 2D memref + std::string recipeName = + getRecipeName(RecipeKind::private_recipe, memref2DTy); + EXPECT_EQ(recipeName, "privatization_memref_5x10xf32_"); +} + +TEST_F(OpenACCUtilsTest, getRecipeNameFirstprivate2DMemref) { + // Create a 2D memref type + auto memref2DTy = MemRefType::get({8, 16}, b.getF64Type()); + + // Test firstprivate recipe with 2D memref + std::string recipeName = + getRecipeName(RecipeKind::firstprivate_recipe, memref2DTy); + EXPECT_EQ(recipeName, "firstprivatization_memref_8x16xf64_"); +} + +TEST_F(OpenACCUtilsTest, getRecipeNameReduction2DMemref) { + // Create a 2D memref type + auto memref2DTy = MemRefType::get({4, 8}, b.getI32Type()); + + // Test reduction recipe with 2D memref + std::string recipeName = + getRecipeName(RecipeKind::reduction_recipe, memref2DTy); + EXPECT_EQ(recipeName, "reduction_memref_4x8xi32_"); +} + +TEST_F(OpenACCUtilsTest, getRecipeNamePrivateDynamicMemref) { + // Create a memref with dynamic dimensions + auto dynamicMemrefTy = + MemRefType::get({ShapedType::kDynamic, 10}, b.getI32Type()); + + // Test private recipe with dynamic memref + std::string recipeName = + getRecipeName(RecipeKind::private_recipe, dynamicMemrefTy); + EXPECT_EQ(recipeName, "privatization_memref_Ux10xi32_"); +} + +TEST_F(OpenACCUtilsTest, getRecipeNamePrivateUnrankedMemref) { + // Create an unranked memref type + auto unrankedMemrefTy = UnrankedMemRefType::get(b.getI32Type(), 0); + + // Test private recipe with unranked memref + std::string recipeName = + getRecipeName(RecipeKind::private_recipe, unrankedMemrefTy); + EXPECT_EQ(recipeName, "privatization_memref_Zxi32_"); +}