238 lines · cpp
1//===- ACCImplicitRoutine.cpp - OpenACC Implicit Routine Transform -------===//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// This pass implements the implicit rules described in OpenACC specification10// for `Routine Directive` (OpenACC 3.4 spec, section 2.15.1).11//12// "If no explicit routine directive applies to a procedure whose definition13// appears in the program unit being compiled, then the implementation applies14// an implicit routine directive to that procedure if any of the following15// conditions holds:16// - The procedure is called or its address is accessed in a compute region."17//18// The specification further states:19// "When the implementation applies an implicit routine directive to a20// procedure, it must recursively apply implicit routine directives to other21// procedures for which the above rules specify relevant dependencies. Such22// dependencies can form a cycle, so the implementation must take care to avoid23// infinite recursion."24//25// This pass implements these requirements by:26// 1. Walking through all OpenACC compute constructs and functions already27// marked with `acc routine` in the module and identifying function calls28// within these regions.29// 2. Creating implicit `acc.routine` operations for functions that don't30// already have routine declarations.31// 3. Recursively walking through all existing `acc routine` and creating32// implicit routine operations for function calls within these routines,33// while avoiding infinite recursion through proper tracking.34//35// Requirements:36// -------------37// To use this pass in a pipeline, the following requirements must be met:38//39// 1. Operation Interface Implementation: Operations that define functions40// or call functions should implement `mlir::FunctionOpInterface` and41// `mlir::CallOpInterface` respectively.42//43// 2. Analysis Registration (Optional): If custom behavior is needed for44// determining if a symbol use is valid within GPU regions, the dialect45// should pre-register the `acc::OpenACCSupport` analysis.46//===----------------------------------------------------------------------===//47 48#include "mlir/Dialect/OpenACC/Transforms/Passes.h"49 50#include "mlir/Dialect/OpenACC/Analysis/OpenACCSupport.h"51#include "mlir/Dialect/OpenACC/OpenACC.h"52#include "mlir/IR/Builders.h"53#include "mlir/IR/BuiltinAttributes.h"54#include "mlir/IR/BuiltinOps.h"55#include "mlir/IR/Operation.h"56#include "mlir/IR/Value.h"57#include "mlir/Interfaces/CallInterfaces.h"58#include "mlir/Interfaces/FunctionInterfaces.h"59#include <queue>60 61#define DEBUG_TYPE "acc-implicit-routine"62 63namespace mlir {64namespace acc {65#define GEN_PASS_DEF_ACCIMPLICITROUTINE66#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"67} // namespace acc68} // namespace mlir69 70namespace {71 72using namespace mlir;73 74class ACCImplicitRoutine75 : public acc::impl::ACCImplicitRoutineBase<ACCImplicitRoutine> {76private:77 unsigned routineCounter = 0;78 static constexpr llvm::StringRef accRoutinePrefix = "acc_routine_";79 80 // Count existing routine operations and update counter81 void initRoutineCounter(ModuleOp module) {82 module.walk([&](acc::RoutineOp routineOp) { routineCounter++; });83 }84 85 // Check if routine has a default bind clause or a device-type specific bind86 // clause. Returns true if `acc routine` has a default bind clause or87 // a device-type specific bind clause.88 bool isACCRoutineBindDefaultOrDeviceType(acc::RoutineOp op,89 acc::DeviceType deviceType) {90 // Fast check to avoid device-type specific lookups.91 if (!op.getBindIdName() && !op.getBindStrName())92 return false;93 return op.getBindNameValue().has_value() ||94 op.getBindNameValue(deviceType).has_value();95 }96 97 // Generate a unique name for the routine and create the routine operation98 acc::RoutineOp createRoutineOp(OpBuilder &builder, Location loc,99 FunctionOpInterface &callee) {100 std::string routineName =101 (accRoutinePrefix + std::to_string(routineCounter++)).str();102 auto routineOp = acc::RoutineOp::create(103 builder, loc,104 /* sym_name=*/builder.getStringAttr(routineName),105 /* func_name=*/106 mlir::SymbolRefAttr::get(builder.getContext(),107 builder.getStringAttr(callee.getName())),108 /* bindIdName=*/nullptr,109 /* bindStrName=*/nullptr,110 /* bindIdNameDeviceType=*/nullptr,111 /* bindStrNameDeviceType=*/nullptr,112 /* worker=*/nullptr,113 /* vector=*/nullptr,114 /* seq=*/nullptr,115 /* nohost=*/nullptr,116 /* implicit=*/builder.getUnitAttr(),117 /* gang=*/nullptr,118 /* gangDim=*/nullptr,119 /* gangDimDeviceType=*/nullptr);120 121 // Assert that the callee does not already have routine info attribute122 assert(!callee->hasAttr(acc::getRoutineInfoAttrName()) &&123 "function is already associated with a routine");124 125 callee->setAttr(126 acc::getRoutineInfoAttrName(),127 mlir::acc::RoutineInfoAttr::get(128 builder.getContext(),129 {mlir::SymbolRefAttr::get(builder.getContext(),130 builder.getStringAttr(routineName))}));131 return routineOp;132 }133 134 // Used to walk through a compute region looking for function calls.135 void136 implicitRoutineForCallsInComputeRegions(Operation *op, SymbolTable &symTab,137 mlir::OpBuilder &builder,138 acc::OpenACCSupport &accSupport) {139 op->walk([&](CallOpInterface callOp) {140 if (!callOp.getCallableForCallee())141 return;142 143 auto calleeSymbolRef =144 dyn_cast<SymbolRefAttr>(callOp.getCallableForCallee());145 // When call is done through ssa value, the callee is not a symbol.146 // Skip it because we don't know the call target.147 if (!calleeSymbolRef)148 return;149 150 auto callee = symTab.lookup<FunctionOpInterface>(151 calleeSymbolRef.getLeafReference().str());152 // If the callee does not exist or is already a valid symbol for GPU153 // regions, skip it154 155 assert(callee && "callee function must be found in symbol table");156 if (accSupport.isValidSymbolUse(callOp.getOperation(), calleeSymbolRef))157 return;158 builder.setInsertionPoint(callee);159 createRoutineOp(builder, callee.getLoc(), callee);160 });161 }162 163 // Recursively handle calls within a routine operation164 void implicitRoutineForCallsInRoutine(acc::RoutineOp routineOp,165 mlir::OpBuilder &builder,166 acc::OpenACCSupport &accSupport,167 acc::DeviceType targetDeviceType) {168 // When bind clause is used, it means that the target is different than the169 // function to which the `acc routine` is used with. Skip this case to170 // avoid implicitly recursively marking calls that would not end up on171 // device.172 if (isACCRoutineBindDefaultOrDeviceType(routineOp, targetDeviceType))173 return;174 175 SymbolTable symTab(routineOp->getParentOfType<ModuleOp>());176 std::queue<acc::RoutineOp> routineQueue;177 routineQueue.push(routineOp);178 while (!routineQueue.empty()) {179 auto currentRoutine = routineQueue.front();180 routineQueue.pop();181 auto func = symTab.lookup<FunctionOpInterface>(182 currentRoutine.getFuncName().getLeafReference());183 func.walk([&](CallOpInterface callOp) {184 if (!callOp.getCallableForCallee())185 return;186 187 auto calleeSymbolRef =188 dyn_cast<SymbolRefAttr>(callOp.getCallableForCallee());189 // When call is done through ssa value, the callee is not a symbol.190 // Skip it because we don't know the call target.191 if (!calleeSymbolRef)192 return;193 194 auto callee = symTab.lookup<FunctionOpInterface>(195 calleeSymbolRef.getLeafReference().str());196 // If the callee does not exist or is already a valid symbol for GPU197 // regions, skip it198 assert(callee && "callee function must be found in symbol table");199 if (accSupport.isValidSymbolUse(callOp.getOperation(), calleeSymbolRef))200 return;201 builder.setInsertionPoint(callee);202 auto newRoutineOp = createRoutineOp(builder, callee.getLoc(), callee);203 routineQueue.push(newRoutineOp);204 });205 }206 }207 208public:209 using ACCImplicitRoutineBase<ACCImplicitRoutine>::ACCImplicitRoutineBase;210 211 void runOnOperation() override {212 auto module = getOperation();213 mlir::OpBuilder builder(module.getContext());214 SymbolTable symTab(module);215 initRoutineCounter(module);216 217 acc::OpenACCSupport &accSupport = getAnalysis<acc::OpenACCSupport>();218 219 // Handle compute regions220 module.walk([&](Operation *op) {221 if (isa<ACC_COMPUTE_CONSTRUCT_OPS>(op))222 implicitRoutineForCallsInComputeRegions(op, symTab, builder,223 accSupport);224 });225 226 // Use the device type option from the pass options.227 acc::DeviceType targetDeviceType = deviceType;228 229 // Handle existing routines230 module.walk([&](acc::RoutineOp routineOp) {231 implicitRoutineForCallsInRoutine(routineOp, builder, accSupport,232 targetDeviceType);233 });234 }235};236 237} // namespace238