208 lines · cpp
1//===- OpenACCUtils.cpp ---------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "mlir/Dialect/OpenACC/OpenACCUtils.h"10 11#include "mlir/Dialect/OpenACC/OpenACC.h"12#include "mlir/IR/SymbolTable.h"13#include "mlir/Interfaces/FunctionInterfaces.h"14#include "mlir/Interfaces/ViewLikeInterface.h"15#include "llvm/ADT/TypeSwitch.h"16#include "llvm/IR/Intrinsics.h"17#include "llvm/Support/Casting.h"18 19mlir::Operation *mlir::acc::getEnclosingComputeOp(mlir::Region ®ion) {20 mlir::Operation *parentOp = region.getParentOp();21 while (parentOp) {22 if (mlir::isa<ACC_COMPUTE_CONSTRUCT_OPS>(parentOp))23 return parentOp;24 parentOp = parentOp->getParentOp();25 }26 return nullptr;27}28 29template <typename OpTy>30static bool isOnlyUsedByOpClauses(mlir::Value val, mlir::Region ®ion) {31 auto checkIfUsedOnlyByOpInside = [&](mlir::Operation *user) {32 // For any users which are not in the current acc region, we can ignore.33 // Return true so that it can be used in a `all_of` check.34 if (!region.isAncestor(user->getParentRegion()))35 return true;36 return mlir::isa<OpTy>(user);37 };38 39 return llvm::all_of(val.getUsers(), checkIfUsedOnlyByOpInside);40}41 42bool mlir::acc::isOnlyUsedByPrivateClauses(mlir::Value val,43 mlir::Region ®ion) {44 return isOnlyUsedByOpClauses<mlir::acc::PrivateOp>(val, region);45}46 47bool mlir::acc::isOnlyUsedByReductionClauses(mlir::Value val,48 mlir::Region ®ion) {49 return isOnlyUsedByOpClauses<mlir::acc::ReductionOp>(val, region);50}51 52std::optional<mlir::acc::ClauseDefaultValue>53mlir::acc::getDefaultAttr(Operation *op) {54 std::optional<mlir::acc::ClauseDefaultValue> defaultAttr;55 Operation *currOp = op;56 57 // Iterate outwards until a default clause is found (since OpenACC58 // specification notes that a visible default clause is the nearest default59 // clause appearing on the compute construct or a lexically containing data60 // construct.61 while (!defaultAttr.has_value() && currOp) {62 defaultAttr =63 llvm::TypeSwitch<mlir::Operation *,64 std::optional<mlir::acc::ClauseDefaultValue>>(currOp)65 .Case<ACC_COMPUTE_CONSTRUCT_OPS, mlir::acc::DataOp>(66 [&](auto op) { return op.getDefaultAttr(); })67 .Default([&](Operation *) { return std::nullopt; });68 currOp = currOp->getParentOp();69 }70 71 return defaultAttr;72}73 74mlir::acc::VariableTypeCategory mlir::acc::getTypeCategory(mlir::Value var) {75 mlir::acc::VariableTypeCategory typeCategory =76 mlir::acc::VariableTypeCategory::uncategorized;77 if (auto mappableTy = dyn_cast<mlir::acc::MappableType>(var.getType()))78 typeCategory = mappableTy.getTypeCategory(var);79 else if (auto pointerLikeTy =80 dyn_cast<mlir::acc::PointerLikeType>(var.getType()))81 typeCategory = pointerLikeTy.getPointeeTypeCategory(82 cast<TypedValue<mlir::acc::PointerLikeType>>(var),83 pointerLikeTy.getElementType());84 return typeCategory;85}86 87std::string mlir::acc::getVariableName(mlir::Value v) {88 Value current = v;89 90 // Walk through view operations until a name is found or can't go further91 while (Operation *definingOp = current.getDefiningOp()) {92 // Check for `acc.var_name` attribute93 if (auto varNameAttr =94 definingOp->getAttrOfType<VarNameAttr>(getVarNameAttrName()))95 return varNameAttr.getName().str();96 97 // If it is a data entry operation, get name via getVarName98 if (isa<ACC_DATA_ENTRY_OPS>(definingOp))99 if (auto name = acc::getVarName(definingOp))100 return name->str();101 102 // If it's a view operation, continue to the source103 if (auto viewOp = dyn_cast<ViewLikeOpInterface>(definingOp)) {104 current = viewOp.getViewSource();105 continue;106 }107 108 break;109 }110 111 return "";112}113 114std::string mlir::acc::getRecipeName(mlir::acc::RecipeKind kind,115 mlir::Type type) {116 assert(kind == mlir::acc::RecipeKind::private_recipe ||117 kind == mlir::acc::RecipeKind::firstprivate_recipe ||118 kind == mlir::acc::RecipeKind::reduction_recipe);119 if (!llvm::isa<mlir::acc::PointerLikeType, mlir::acc::MappableType>(type))120 return "";121 122 std::string recipeName;123 llvm::raw_string_ostream ss(recipeName);124 ss << (kind == mlir::acc::RecipeKind::private_recipe ? "privatization_"125 : kind == mlir::acc::RecipeKind::firstprivate_recipe126 ? "firstprivatization_"127 : "reduction_");128 129 // Print the type using its dialect-defined textual format.130 type.print(ss);131 ss.flush();132 133 // Replace invalid characters (anything that's not a letter, number, or134 // period) since this needs to be a valid MLIR identifier.135 for (char &c : recipeName) {136 if (!std::isalnum(static_cast<unsigned char>(c)) && c != '.' && c != '_') {137 if (c == '?')138 c = 'U';139 else if (c == '*')140 c = 'Z';141 else if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' ||142 c == '}' || c == '<' || c == '>')143 c = '_';144 else145 c = 'X';146 }147 }148 149 return recipeName;150}151 152mlir::Value mlir::acc::getBaseEntity(mlir::Value val) {153 if (auto partialEntityAccessOp =154 dyn_cast<PartialEntityAccessOpInterface>(val.getDefiningOp())) {155 if (!partialEntityAccessOp.isCompleteView())156 return partialEntityAccessOp.getBaseEntity();157 }158 159 return val;160}161 162bool mlir::acc::isValidSymbolUse(mlir::Operation *user,163 mlir::SymbolRefAttr symbol,164 mlir::Operation **definingOpPtr) {165 mlir::Operation *definingOp =166 mlir::SymbolTable::lookupNearestSymbolFrom(user, symbol);167 168 // If there are no defining ops, we have no way to ensure validity because169 // we cannot check for any attributes.170 if (!definingOp)171 return false;172 173 if (definingOpPtr)174 *definingOpPtr = definingOp;175 176 // Check if the defining op is a recipe (private, reduction, firstprivate).177 // Recipes are valid as they get materialized before being offloaded to178 // device. They are only instructions for how to materialize.179 if (mlir::isa<mlir::acc::PrivateRecipeOp, mlir::acc::ReductionRecipeOp,180 mlir::acc::FirstprivateRecipeOp>(definingOp))181 return true;182 183 // Check if the defining op is a function184 if (auto func =185 mlir::dyn_cast_if_present<mlir::FunctionOpInterface>(definingOp)) {186 // If this symbol is actually an acc routine - then it is expected for it187 // to be offloaded - therefore it is valid.188 if (func->hasAttr(mlir::acc::getRoutineInfoAttrName()))189 return true;190 191 // If this symbol is a call to an LLVM intrinsic, then it is likely valid.192 // Check the following:193 // 1. The function is private194 // 2. The function has no body195 // 3. Name starts with "llvm."196 // 4. The function's name is a valid LLVM intrinsic name197 if (func.getVisibility() == mlir::SymbolTable::Visibility::Private &&198 func.getFunctionBody().empty() && func.getName().starts_with("llvm.") &&199 llvm::Intrinsic::lookupIntrinsicID(func.getName()) !=200 llvm::Intrinsic::not_intrinsic)201 return true;202 }203 204 // A declare attribute is needed for symbol references.205 bool hasDeclare = definingOp->hasAttr(mlir::acc::getDeclareAttrName());206 return hasDeclare;207}208