For OpenACC clause ordering, such as maintaining appropriate parent-child relationship ordering, we need to be able to walk references back to their base entities. This introduces the operation interface in the `acc` dialect named `PartialEntityAccessOpInterface` which can be used for this purpose. The interface provides two methods: - `getBaseEntity()`: Returns the base entity being accessed - `isCompleteView()`: Indicates whether the access covers the complete entity to allow this interface to be attached to cases that only conditionally offer a partial view This also adds a utility function `mlir::acc::getBaseEntity()` that uses this interface to retrieve the base entity from a value. This work has some similarities with the ViewLikeOpInterface proposal for FIR: https://github.com/llvm/llvm-project/pull/164020 but it differs in the following ways: - Attached only to operations where we can assume a partial entity access - Includes fir.declare operations due to common block storage associations Tests are included that demonstrate the interface on memref.subview operations, implemented locally in the test since memref operations already have ViewLikeOpInterface for similar purposes.
158 lines
5.5 KiB
C++
158 lines
5.5 KiB
C++
//===- OpenACCUtils.cpp ---------------------------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "mlir/Dialect/OpenACC/OpenACCUtils.h"
|
|
|
|
#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();
|
|
while (parentOp) {
|
|
if (mlir::isa<ACC_COMPUTE_CONSTRUCT_OPS>(parentOp))
|
|
return parentOp;
|
|
parentOp = parentOp->getParentOp();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
template <typename OpTy>
|
|
static bool isOnlyUsedByOpClauses(mlir::Value val, mlir::Region ®ion) {
|
|
auto checkIfUsedOnlyByOpInside = [&](mlir::Operation *user) {
|
|
// For any users which are not in the current acc region, we can ignore.
|
|
// Return true so that it can be used in a `all_of` check.
|
|
if (!region.isAncestor(user->getParentRegion()))
|
|
return true;
|
|
return mlir::isa<OpTy>(user);
|
|
};
|
|
|
|
return llvm::all_of(val.getUsers(), checkIfUsedOnlyByOpInside);
|
|
}
|
|
|
|
bool mlir::acc::isOnlyUsedByPrivateClauses(mlir::Value val,
|
|
mlir::Region ®ion) {
|
|
return isOnlyUsedByOpClauses<mlir::acc::PrivateOp>(val, region);
|
|
}
|
|
|
|
bool mlir::acc::isOnlyUsedByReductionClauses(mlir::Value val,
|
|
mlir::Region ®ion) {
|
|
return isOnlyUsedByOpClauses<mlir::acc::ReductionOp>(val, region);
|
|
}
|
|
|
|
std::optional<mlir::acc::ClauseDefaultValue>
|
|
mlir::acc::getDefaultAttr(Operation *op) {
|
|
std::optional<mlir::acc::ClauseDefaultValue> defaultAttr;
|
|
Operation *currOp = op;
|
|
|
|
// Iterate outwards until a default clause is found (since OpenACC
|
|
// specification notes that a visible default clause is the nearest default
|
|
// clause appearing on the compute construct or a lexically containing data
|
|
// construct.
|
|
while (!defaultAttr.has_value() && currOp) {
|
|
defaultAttr =
|
|
llvm::TypeSwitch<mlir::Operation *,
|
|
std::optional<mlir::acc::ClauseDefaultValue>>(currOp)
|
|
.Case<ACC_COMPUTE_CONSTRUCT_OPS, mlir::acc::DataOp>(
|
|
[&](auto op) { return op.getDefaultAttr(); })
|
|
.Default([&](Operation *) { return std::nullopt; });
|
|
currOp = currOp->getParentOp();
|
|
}
|
|
|
|
return defaultAttr;
|
|
}
|
|
|
|
mlir::acc::VariableTypeCategory mlir::acc::getTypeCategory(mlir::Value var) {
|
|
mlir::acc::VariableTypeCategory typeCategory =
|
|
mlir::acc::VariableTypeCategory::uncategorized;
|
|
if (auto mappableTy = dyn_cast<mlir::acc::MappableType>(var.getType()))
|
|
typeCategory = mappableTy.getTypeCategory(var);
|
|
else if (auto pointerLikeTy =
|
|
dyn_cast<mlir::acc::PointerLikeType>(var.getType()))
|
|
typeCategory = pointerLikeTy.getPointeeTypeCategory(
|
|
cast<TypedValue<mlir::acc::PointerLikeType>>(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 "";
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
mlir::Value mlir::acc::getBaseEntity(mlir::Value val) {
|
|
if (auto partialEntityAccessOp =
|
|
dyn_cast<PartialEntityAccessOpInterface>(val.getDefiningOp())) {
|
|
if (!partialEntityAccessOp.isCompleteView())
|
|
return partialEntityAccessOp.getBaseEntity();
|
|
}
|
|
|
|
return val;
|
|
}
|