//===- AMDGPUPreloadKernelArguments.cpp - Preload Kernel Arguments --------===// // // 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 // //===----------------------------------------------------------------------===// // /// \file This pass preloads kernel arguments into user_data SGPRs before kernel /// execution begins. The number of registers available for preloading depends /// on the number of free user SGPRs, up to the hardware's maximum limit. /// Implicit arguments enabled in the kernel descriptor are allocated first, /// followed by SGPRs used for preloaded kernel arguments. (Reference: /// https://llvm.org/docs/AMDGPUUsage.html#initial-kernel-execution-state) /// Additionally, hidden kernel arguments may be preloaded, in which case they /// are appended to the kernel signature after explicit arguments. Preloaded /// arguments will be marked with `inreg`. // //===----------------------------------------------------------------------===// #include "AMDGPU.h" #include "AMDGPUTargetMachine.h" #include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/Function.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicsAMDGPU.h" #include "llvm/IR/Module.h" #include "llvm/IR/PassManager.h" #include "llvm/IR/Verifier.h" #include "llvm/Pass.h" #define DEBUG_TYPE "amdgpu-preload-kernel-arguments" using namespace llvm; static cl::opt KernargPreloadCount( "amdgpu-kernarg-preload-count", cl::desc("How many kernel arguments to preload onto SGPRs"), cl::init(0)); namespace { class AMDGPUPreloadKernelArgumentsLegacy : public ModulePass { const GCNTargetMachine *TM; public: static char ID; explicit AMDGPUPreloadKernelArgumentsLegacy( const GCNTargetMachine *TM = nullptr); StringRef getPassName() const override { return "AMDGPU Preload Kernel Arguments"; } bool runOnModule(Module &M) override; }; class PreloadKernelArgInfo { private: Function &F; const GCNSubtarget &ST; unsigned NumFreeUserSGPRs; enum HiddenArg : unsigned { HIDDEN_BLOCK_COUNT_X, HIDDEN_BLOCK_COUNT_Y, HIDDEN_BLOCK_COUNT_Z, HIDDEN_GROUP_SIZE_X, HIDDEN_GROUP_SIZE_Y, HIDDEN_GROUP_SIZE_Z, HIDDEN_REMAINDER_X, HIDDEN_REMAINDER_Y, HIDDEN_REMAINDER_Z, END_HIDDEN_ARGS }; // Stores information about a specific hidden argument. struct HiddenArgInfo { // Offset in bytes from the location in the kernearg segment pointed to by // the implicitarg pointer. uint8_t Offset; // The size of the hidden argument in bytes. uint8_t Size; // The name of the hidden argument in the kernel signature. const char *Name; }; static constexpr HiddenArgInfo HiddenArgs[END_HIDDEN_ARGS] = { {0, 4, "_hidden_block_count_x"}, {4, 4, "_hidden_block_count_y"}, {8, 4, "_hidden_block_count_z"}, {12, 2, "_hidden_group_size_x"}, {14, 2, "_hidden_group_size_y"}, {16, 2, "_hidden_group_size_z"}, {18, 2, "_hidden_remainder_x"}, {20, 2, "_hidden_remainder_y"}, {22, 2, "_hidden_remainder_z"}}; static HiddenArg getHiddenArgFromOffset(unsigned Offset) { for (unsigned I = 0; I < END_HIDDEN_ARGS; ++I) if (HiddenArgs[I].Offset == Offset) return static_cast(I); return END_HIDDEN_ARGS; } static Type *getHiddenArgType(LLVMContext &Ctx, HiddenArg HA) { if (HA < END_HIDDEN_ARGS) return Type::getIntNTy(Ctx, HiddenArgs[HA].Size * 8); llvm_unreachable("Unexpected hidden argument."); } static const char *getHiddenArgName(HiddenArg HA) { if (HA < END_HIDDEN_ARGS) return HiddenArgs[HA].Name; llvm_unreachable("Unexpected hidden argument."); } // Clones the function after adding implicit arguments to the argument list // and returns the new updated function. Preloaded implicit arguments are // added up to and including the last one that will be preloaded, indicated by // LastPreloadIndex. Currently preloading is only performed on the totality of // sequential data from the kernarg segment including implicit (hidden) // arguments. This means that all arguments up to the last preloaded argument // will also be preloaded even if that data is unused. Function *cloneFunctionWithPreloadImplicitArgs(unsigned LastPreloadIndex) { FunctionType *FT = F.getFunctionType(); LLVMContext &Ctx = F.getParent()->getContext(); SmallVector FTypes(FT->param_begin(), FT->param_end()); for (unsigned I = 0; I <= LastPreloadIndex; ++I) FTypes.push_back(getHiddenArgType(Ctx, HiddenArg(I))); FunctionType *NFT = FunctionType::get(FT->getReturnType(), FTypes, FT->isVarArg()); Function *NF = Function::Create(NFT, F.getLinkage(), F.getAddressSpace(), F.getName()); NF->copyAttributesFrom(&F); NF->copyMetadata(&F, 0); F.getParent()->getFunctionList().insert(F.getIterator(), NF); NF->takeName(&F); NF->splice(NF->begin(), &F); Function::arg_iterator NFArg = NF->arg_begin(); for (Argument &Arg : F.args()) { Arg.replaceAllUsesWith(&*NFArg); NFArg->takeName(&Arg); ++NFArg; } AttrBuilder AB(Ctx); AB.addAttribute(Attribute::InReg); AB.addAttribute("amdgpu-hidden-argument"); AttributeList AL = NF->getAttributes(); for (unsigned I = 0; I <= LastPreloadIndex; ++I) { AL = AL.addParamAttributes(Ctx, NFArg->getArgNo(), AB); NFArg++->setName(getHiddenArgName(HiddenArg(I))); } NF->setAttributes(AL); F.replaceAllUsesWith(NF); return NF; } public: PreloadKernelArgInfo(Function &F, const GCNSubtarget &ST) : F(F), ST(ST) { setInitialFreeUserSGPRsCount(); } // Returns the maximum number of user SGPRs that we have available to preload // arguments. void setInitialFreeUserSGPRsCount() { GCNUserSGPRUsageInfo UserSGPRInfo(F, ST); NumFreeUserSGPRs = UserSGPRInfo.getNumFreeUserSGPRs(); } bool canPreloadKernArgAtOffset(uint64_t ExplicitArgOffset) { return ExplicitArgOffset <= NumFreeUserSGPRs * 4; } // Try to allocate SGPRs to preload hidden kernel arguments. void tryAllocHiddenArgPreloadSGPRs(uint64_t ImplicitArgsBaseOffset, SmallVectorImpl &FunctionsToErase) { Function *ImplicitArgPtr = Intrinsic::getDeclarationIfExists( F.getParent(), Intrinsic::amdgcn_implicitarg_ptr); if (!ImplicitArgPtr) return; const DataLayout &DL = F.getParent()->getDataLayout(); // Pair is the load and the load offset. SmallVector, 4> ImplicitArgLoads; for (auto *U : ImplicitArgPtr->users()) { Instruction *CI = dyn_cast(U); if (!CI || CI->getParent()->getParent() != &F) continue; for (auto *U : CI->users()) { int64_t Offset = 0; auto *Load = dyn_cast(U); // Load from ImplicitArgPtr? if (!Load) { if (GetPointerBaseWithConstantOffset(U, Offset, DL) != CI) continue; Load = dyn_cast(*U->user_begin()); // Load from GEP? } if (!Load || !Load->isSimple()) continue; // FIXME: Expand handle merged loads. LLVMContext &Ctx = F.getParent()->getContext(); Type *LoadTy = Load->getType(); HiddenArg HA = getHiddenArgFromOffset(Offset); if (HA == END_HIDDEN_ARGS || LoadTy != getHiddenArgType(Ctx, HA)) continue; ImplicitArgLoads.push_back(std::make_pair(Load, Offset)); } } if (ImplicitArgLoads.empty()) return; // Allocate loads in order of offset. We need to be sure that the implicit // argument can actually be preloaded. std::sort(ImplicitArgLoads.begin(), ImplicitArgLoads.end(), less_second()); // If we fail to preload any implicit argument we know we don't have SGPRs // to preload any subsequent ones with larger offsets. Find the first // argument that we cannot preload. auto *PreloadEnd = llvm::find_if( ImplicitArgLoads, [&](const std::pair &Load) { unsigned LoadSize = DL.getTypeStoreSize(Load.first->getType()); unsigned LoadOffset = Load.second; if (!canPreloadKernArgAtOffset(LoadOffset + LoadSize + ImplicitArgsBaseOffset)) return true; return false; }); if (PreloadEnd == ImplicitArgLoads.begin()) return; unsigned LastHiddenArgIndex = getHiddenArgFromOffset(PreloadEnd[-1].second); Function *NF = cloneFunctionWithPreloadImplicitArgs(LastHiddenArgIndex); assert(NF); FunctionsToErase.push_back(&F); for (const auto *I = ImplicitArgLoads.begin(); I != PreloadEnd; ++I) { LoadInst *LoadInst = I->first; unsigned LoadOffset = I->second; unsigned HiddenArgIndex = getHiddenArgFromOffset(LoadOffset); unsigned Index = NF->arg_size() - LastHiddenArgIndex + HiddenArgIndex - 1; Argument *Arg = NF->getArg(Index); LoadInst->replaceAllUsesWith(Arg); } } }; } // end anonymous namespace char AMDGPUPreloadKernelArgumentsLegacy::ID = 0; INITIALIZE_PASS(AMDGPUPreloadKernelArgumentsLegacy, DEBUG_TYPE, "AMDGPU Preload Kernel Arguments", false, false) ModulePass * llvm::createAMDGPUPreloadKernelArgumentsLegacyPass(const TargetMachine *TM) { return new AMDGPUPreloadKernelArgumentsLegacy( static_cast(TM)); } AMDGPUPreloadKernelArgumentsLegacy::AMDGPUPreloadKernelArgumentsLegacy( const GCNTargetMachine *TM) : ModulePass(ID), TM(TM) {} static bool markKernelArgsAsInreg(Module &M, const TargetMachine &TM) { SmallVector FunctionsToErase; bool Changed = false; for (auto &F : M) { const GCNSubtarget &ST = TM.getSubtarget(F); if (!ST.hasKernargPreload() || F.getCallingConv() != CallingConv::AMDGPU_KERNEL) continue; PreloadKernelArgInfo PreloadInfo(F, ST); uint64_t ExplicitArgOffset = 0; const DataLayout &DL = F.getDataLayout(); const uint64_t BaseOffset = ST.getExplicitKernelArgOffset(); unsigned NumPreloadsRequested = KernargPreloadCount; unsigned NumPreloadedExplicitArgs = 0; for (Argument &Arg : F.args()) { // Avoid incompatible attributes and guard against running this pass // twice. // // TODO: Preload byref kernel arguments if (Arg.hasByRefAttr() || Arg.hasNestAttr() || Arg.hasAttribute("amdgpu-hidden-argument")) break; // Inreg may be pre-existing on some arguments, try to preload these. if (NumPreloadsRequested == 0 && !Arg.hasInRegAttr()) break; // FIXME: Preload aggregates. if (Arg.getType()->isAggregateType()) break; Type *ArgTy = Arg.getType(); Align ABITypeAlign = DL.getABITypeAlign(ArgTy); uint64_t AllocSize = DL.getTypeAllocSize(ArgTy); ExplicitArgOffset = alignTo(ExplicitArgOffset, ABITypeAlign) + AllocSize; if (!PreloadInfo.canPreloadKernArgAtOffset(ExplicitArgOffset)) break; Arg.addAttr(Attribute::InReg); NumPreloadedExplicitArgs++; if (NumPreloadsRequested > 0) NumPreloadsRequested--; } // Only try preloading hidden arguments if we can successfully preload the // last explicit argument. if (NumPreloadedExplicitArgs == F.arg_size()) { uint64_t ImplicitArgsBaseOffset = alignTo(ExplicitArgOffset, ST.getAlignmentForImplicitArgPtr()) + BaseOffset; PreloadInfo.tryAllocHiddenArgPreloadSGPRs(ImplicitArgsBaseOffset, FunctionsToErase); } Changed |= NumPreloadedExplicitArgs > 0; } // Erase cloned functions if we needed to update the kernel signature to // support preloading hidden kernel arguments. for (auto *F : FunctionsToErase) F->eraseFromParent(); return Changed; } bool AMDGPUPreloadKernelArgumentsLegacy::runOnModule(Module &M) { if (skipModule(M) || !TM) return false; return markKernelArgsAsInreg(M, *TM); } PreservedAnalyses AMDGPUPreloadKernelArgumentsPass::run(Module &M, ModuleAnalysisManager &AM) { bool Changed = markKernelArgsAsInreg(M, TM); return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all(); }