Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions mlir/include/mlir/Dialect/OpenACC/Analysis/OpenACCSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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;
};
Expand Down Expand Up @@ -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) {
Expand Down
20 changes: 20 additions & 0 deletions mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -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<OpenACC_Dialect,
OpenACC_RecipeKind,
"recipe_kind"> {
let assemblyFormat = [{ ```<` $value `>` }];
}

// Type used in operation below.
def IntOrIndex : AnyTypeOf<[AnyInteger, Index]>;

Expand Down
4 changes: 4 additions & 0 deletions mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 19 additions & 0 deletions mlir/lib/Dialect/OpenACC/Analysis/OpenACCSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 8 additions & 5 deletions mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ static void attachVarNameAttr(Operation *op, OpBuilder &builder,
}
}

template <typename T>
struct MemRefPointerLikeModel
: public PointerLikeType::ExternalModel<MemRefPointerLikeModel,
MemRefType> {
: public PointerLikeType::ExternalModel<MemRefPointerLikeModel<T>, T> {
Type getElementType(Type pointer) const {
return cast<MemRefType>(pointer).getElementType();
return cast<T>(pointer).getElementType();
}

mlir::acc::VariableTypeCategory
Expand All @@ -63,7 +63,7 @@ struct MemRefPointerLikeModel
if (auto mappableTy = dyn_cast<MappableType>(varType)) {
return mappableTy.getTypeCategory(varPtr);
}
auto memrefTy = cast<MemRefType>(pointer);
auto memrefTy = cast<T>(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.
Expand Down Expand Up @@ -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<MemRefPointerLikeModel>(*getContext());
MemRefType::attachInterface<MemRefPointerLikeModel<MemRefType>>(
*getContext());
UnrankedMemRefType::attachInterface<
MemRefPointerLikeModel<UnrankedMemRefType>>(*getContext());
LLVM::LLVMPointerType::attachInterface<LLVMPointerPointerLikeModel>(
*getContext());
}
Expand Down
39 changes: 39 additions & 0 deletions mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 &region) {
mlir::Operation *parentOp = region.getParentOp();
Expand Down Expand Up @@ -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<mlir::acc::PointerLikeType, mlir::acc::MappableType>(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<unsigned char>(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;
}
78 changes: 78 additions & 0 deletions mlir/test/Dialect/OpenACC/support-analysis-recipename.mlir
Original file line number Diff line number Diff line change
@@ -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<private_recipe>} : memref<5x10xf32>

// CHECK: op=%{{.*}} = memref.alloca() {test.recipe_name = #acc.recipe_kind<private_recipe>} : 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<firstprivate_recipe>} : memref<8x16xf64>

// CHECK: op=%{{.*}} = memref.alloca() {test.recipe_name = #acc.recipe_kind<firstprivate_recipe>} : 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<reduction_recipe>} : memref<4x8xi32>

// CHECK: op=%{{.*}} = memref.alloca() {test.recipe_name = #acc.recipe_kind<reduction_recipe>} : 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<private_recipe>} : memref<5x10xi32> to memref<?x10xi32>

// CHECK: op=%{{.*}} = memref.cast %{{.*}} {test.recipe_name = #acc.recipe_kind<private_recipe>} : memref<5x10xi32> to memref<?x10xi32>
// CHECK-NEXT: getRecipeName(kind=private_recipe, type=memref<?x10xi32>)="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<private_recipe>} : memref<i32>

// CHECK: op=%{{.*}} = memref.alloca() {test.recipe_name = #acc.recipe_kind<private_recipe>} : memref<i32>
// CHECK-NEXT: getRecipeName(kind=private_recipe, type=memref<i32>)="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<private_recipe>} : memref<10xi32> to memref<*xi32>

// CHECK: op=%{{.*}} = memref.cast %{{.*}} {test.recipe_name = #acc.recipe_kind<private_recipe>} : memref<10xi32> to memref<*xi32>
// CHECK-NEXT: getRecipeName(kind=private_recipe, type=memref<*xi32>)="privatization_memref_Zxi32_"

return
}

18 changes: 18 additions & 0 deletions mlir/test/Dialect/OpenACC/support-analysis-unsupported.mlir
Original file line number Diff line number Diff line change
@@ -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<i32>
// expected-error @below {{not yet implemented: variable privatization (incomplete recipe name handling)}}
%1 = memref.load %0[] {test.recipe_name = #acc.recipe_kind<private_recipe>} : memref<i32>
return
}
22 changes: 22 additions & 0 deletions mlir/test/lib/Dialect/OpenACC/TestOpenACCSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<RecipeKindAttr>("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<StringAttr>("test.emit_nyi")) {
support.emitNYI(op->getLoc(), messageAttr.getValue());
}
});
}

Expand Down
Loading