1 //===- AMDGPUAttributor.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-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 /// \file This pass uses Attributor framework to deduce AMDGPU attributes. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "AMDGPU.h" 14 #include "GCNSubtarget.h" 15 #include "llvm/CodeGen/TargetPassConfig.h" 16 #include "llvm/IR/IntrinsicsAMDGPU.h" 17 #include "llvm/IR/IntrinsicsR600.h" 18 #include "llvm/Target/TargetMachine.h" 19 #include "llvm/Transforms/IPO/Attributor.h" 20 21 #define DEBUG_TYPE "amdgpu-attributor" 22 23 using namespace llvm; 24 25 static constexpr StringLiteral ImplicitAttrNames[] = { 26 // X ids unnecessarily propagated to kernels. 27 "amdgpu-work-item-id-x", "amdgpu-work-item-id-y", 28 "amdgpu-work-item-id-z", "amdgpu-work-group-id-x", 29 "amdgpu-work-group-id-y", "amdgpu-work-group-id-z", 30 "amdgpu-dispatch-ptr", "amdgpu-dispatch-id", 31 "amdgpu-queue-ptr", "amdgpu-implicitarg-ptr"}; 32 33 // We do not need to note the x workitem or workgroup id because they are always 34 // initialized. 35 // 36 // TODO: We should not add the attributes if the known compile time workgroup 37 // size is 1 for y/z. 38 static StringRef intrinsicToAttrName(Intrinsic::ID ID, bool &NonKernelOnly, 39 bool &IsQueuePtr) { 40 switch (ID) { 41 case Intrinsic::amdgcn_workitem_id_x: 42 NonKernelOnly = true; 43 return "amdgpu-work-item-id-x"; 44 case Intrinsic::amdgcn_workgroup_id_x: 45 NonKernelOnly = true; 46 return "amdgpu-work-group-id-x"; 47 case Intrinsic::amdgcn_workitem_id_y: 48 case Intrinsic::r600_read_tidig_y: 49 return "amdgpu-work-item-id-y"; 50 case Intrinsic::amdgcn_workitem_id_z: 51 case Intrinsic::r600_read_tidig_z: 52 return "amdgpu-work-item-id-z"; 53 case Intrinsic::amdgcn_workgroup_id_y: 54 case Intrinsic::r600_read_tgid_y: 55 return "amdgpu-work-group-id-y"; 56 case Intrinsic::amdgcn_workgroup_id_z: 57 case Intrinsic::r600_read_tgid_z: 58 return "amdgpu-work-group-id-z"; 59 case Intrinsic::amdgcn_dispatch_ptr: 60 return "amdgpu-dispatch-ptr"; 61 case Intrinsic::amdgcn_dispatch_id: 62 return "amdgpu-dispatch-id"; 63 case Intrinsic::amdgcn_kernarg_segment_ptr: 64 return "amdgpu-kernarg-segment-ptr"; 65 case Intrinsic::amdgcn_implicitarg_ptr: 66 return "amdgpu-implicitarg-ptr"; 67 case Intrinsic::amdgcn_queue_ptr: 68 case Intrinsic::amdgcn_is_shared: 69 case Intrinsic::amdgcn_is_private: 70 // TODO: Does not require queue ptr on gfx9+ 71 case Intrinsic::trap: 72 case Intrinsic::debugtrap: 73 IsQueuePtr = true; 74 return "amdgpu-queue-ptr"; 75 default: 76 return ""; 77 } 78 } 79 80 static bool castRequiresQueuePtr(unsigned SrcAS) { 81 return SrcAS == AMDGPUAS::LOCAL_ADDRESS || SrcAS == AMDGPUAS::PRIVATE_ADDRESS; 82 } 83 84 static bool isDSAddress(const Constant *C) { 85 const GlobalValue *GV = dyn_cast<GlobalValue>(C); 86 if (!GV) 87 return false; 88 unsigned AS = GV->getAddressSpace(); 89 return AS == AMDGPUAS::LOCAL_ADDRESS || AS == AMDGPUAS::REGION_ADDRESS; 90 } 91 92 class AMDGPUInformationCache : public InformationCache { 93 public: 94 AMDGPUInformationCache(const Module &M, AnalysisGetter &AG, 95 BumpPtrAllocator &Allocator, 96 SetVector<Function *> *CGSCC, TargetMachine &TM) 97 : InformationCache(M, AG, Allocator, CGSCC), TM(TM) {} 98 TargetMachine &TM; 99 100 enum ConstantStatus { DS_GLOBAL = 1 << 0, ADDR_SPACE_CAST = 1 << 1 }; 101 102 /// Check if the subtarget has aperture regs. 103 bool hasApertureRegs(Function &F) { 104 const GCNSubtarget &ST = TM.getSubtarget<GCNSubtarget>(F); 105 return ST.hasApertureRegs(); 106 } 107 108 private: 109 /// Check if the ConstantExpr \p CE requires queue ptr attribute. 110 static bool visitConstExpr(const ConstantExpr *CE) { 111 if (CE->getOpcode() == Instruction::AddrSpaceCast) { 112 unsigned SrcAS = CE->getOperand(0)->getType()->getPointerAddressSpace(); 113 return castRequiresQueuePtr(SrcAS); 114 } 115 return false; 116 } 117 118 /// Get the constant access bitmap for \p C. 119 uint8_t getConstantAccess(const Constant *C) { 120 auto It = ConstantStatus.find(C); 121 if (It != ConstantStatus.end()) 122 return It->second; 123 124 uint8_t Result = 0; 125 if (isDSAddress(C)) 126 Result = DS_GLOBAL; 127 128 if (const auto *CE = dyn_cast<ConstantExpr>(C)) 129 if (visitConstExpr(CE)) 130 Result |= ADDR_SPACE_CAST; 131 132 for (const Use &U : C->operands()) { 133 const auto *OpC = dyn_cast<Constant>(U); 134 if (!OpC) 135 continue; 136 137 Result |= getConstantAccess(OpC); 138 } 139 return Result; 140 } 141 142 public: 143 /// Returns true if \p Fn needs a queue ptr attribute because of \p C. 144 bool needsQueuePtr(const Constant *C, Function &Fn) { 145 bool IsNonEntryFunc = !AMDGPU::isEntryFunctionCC(Fn.getCallingConv()); 146 bool HasAperture = hasApertureRegs(Fn); 147 148 // No need to explore the constants. 149 if (!IsNonEntryFunc && HasAperture) 150 return false; 151 152 uint8_t Access = getConstantAccess(C); 153 154 // We need to trap on DS globals in non-entry functions. 155 if (IsNonEntryFunc && (Access & DS_GLOBAL)) 156 return true; 157 158 return !HasAperture && (Access & ADDR_SPACE_CAST); 159 } 160 161 private: 162 /// Used to determine if the Constant needs a queue ptr attribute. 163 DenseMap<const Constant *, uint8_t> ConstantStatus; 164 }; 165 166 struct AAAMDAttributes : public StateWrapper<BooleanState, AbstractAttribute> { 167 using Base = StateWrapper<BooleanState, AbstractAttribute>; 168 AAAMDAttributes(const IRPosition &IRP, Attributor &A) : Base(IRP) {} 169 170 /// Create an abstract attribute view for the position \p IRP. 171 static AAAMDAttributes &createForPosition(const IRPosition &IRP, 172 Attributor &A); 173 174 /// See AbstractAttribute::getName(). 175 const std::string getName() const override { return "AAAMDAttributes"; } 176 177 /// See AbstractAttribute::getIdAddr(). 178 const char *getIdAddr() const override { return &ID; } 179 180 /// This function should return true if the type of the \p AA is 181 /// AAAMDAttributes. 182 static bool classof(const AbstractAttribute *AA) { 183 return (AA->getIdAddr() == &ID); 184 } 185 186 virtual const DenseSet<StringRef> &getAttributes() const = 0; 187 188 /// Unique ID (due to the unique address) 189 static const char ID; 190 }; 191 const char AAAMDAttributes::ID = 0; 192 193 struct AAAMDWorkGroupSize 194 : public StateWrapper<BooleanState, AbstractAttribute> { 195 using Base = StateWrapper<BooleanState, AbstractAttribute>; 196 AAAMDWorkGroupSize(const IRPosition &IRP, Attributor &A) : Base(IRP) {} 197 198 /// Create an abstract attribute view for the position \p IRP. 199 static AAAMDWorkGroupSize &createForPosition(const IRPosition &IRP, 200 Attributor &A); 201 202 /// See AbstractAttribute::getName(). 203 const std::string getName() const override { return "AAAMDWorkGroupSize"; } 204 205 /// See AbstractAttribute::getIdAddr(). 206 const char *getIdAddr() const override { return &ID; } 207 208 /// This function should return true if the type of the \p AA is 209 /// AAAMDAttributes. 210 static bool classof(const AbstractAttribute *AA) { 211 return (AA->getIdAddr() == &ID); 212 } 213 214 /// Unique ID (due to the unique address) 215 static const char ID; 216 }; 217 const char AAAMDWorkGroupSize::ID = 0; 218 219 struct AAAMDWorkGroupSizeFunction : public AAAMDWorkGroupSize { 220 AAAMDWorkGroupSizeFunction(const IRPosition &IRP, Attributor &A) 221 : AAAMDWorkGroupSize(IRP, A) {} 222 223 void initialize(Attributor &A) override { 224 Function *F = getAssociatedFunction(); 225 CallingConv::ID CC = F->getCallingConv(); 226 227 if (CC != CallingConv::AMDGPU_KERNEL) 228 return; 229 230 bool InitialValue = false; 231 if (F->hasFnAttribute("uniform-work-group-size")) 232 InitialValue = F->getFnAttribute("uniform-work-group-size") 233 .getValueAsString() 234 .equals("true"); 235 236 if (InitialValue) 237 indicateOptimisticFixpoint(); 238 else 239 indicatePessimisticFixpoint(); 240 } 241 242 ChangeStatus updateImpl(Attributor &A) override { 243 Function *F = getAssociatedFunction(); 244 ChangeStatus Change = ChangeStatus::UNCHANGED; 245 246 auto CheckCallSite = [&](AbstractCallSite CS) { 247 Function *Caller = CS.getInstruction()->getFunction(); 248 LLVM_DEBUG(dbgs() << "[AAAMDWorkGroupSize] Call " << Caller->getName() 249 << "->" << F->getName() << "\n"); 250 251 const auto &CallerInfo = A.getAAFor<AAAMDWorkGroupSize>( 252 *this, IRPosition::function(*Caller), DepClassTy::REQUIRED); 253 254 Change = Change | clampStateAndIndicateChange(this->getState(), 255 CallerInfo.getState()); 256 257 return true; 258 }; 259 260 bool AllCallSitesKnown = true; 261 if (!A.checkForAllCallSites(CheckCallSite, *this, true, AllCallSitesKnown)) 262 indicatePessimisticFixpoint(); 263 264 return Change; 265 } 266 267 ChangeStatus manifest(Attributor &A) override { 268 SmallVector<Attribute, 8> AttrList; 269 LLVMContext &Ctx = getAssociatedFunction()->getContext(); 270 271 AttrList.push_back(Attribute::get(Ctx, "uniform-work-group-size", 272 getAssumed() ? "true" : "false")); 273 return IRAttributeManifest::manifestAttrs(A, getIRPosition(), AttrList, 274 /* ForceReplace */ true); 275 } 276 277 bool isValidState() const override { 278 // This state is always valid, even when the state is false. 279 return true; 280 } 281 282 const std::string getAsStr() const override { 283 return "AMDWorkGroupSize[" + std::to_string(getAssumed()) + "]"; 284 } 285 286 /// See AbstractAttribute::trackStatistics() 287 void trackStatistics() const override {} 288 }; 289 290 AAAMDWorkGroupSize &AAAMDWorkGroupSize::createForPosition(const IRPosition &IRP, 291 Attributor &A) { 292 if (IRP.getPositionKind() == IRPosition::IRP_FUNCTION) 293 return *new (A.Allocator) AAAMDWorkGroupSizeFunction(IRP, A); 294 llvm_unreachable("AAAMDWorkGroupSize is only valid for function position"); 295 } 296 297 struct AAAMDAttributesFunction : public AAAMDAttributes { 298 AAAMDAttributesFunction(const IRPosition &IRP, Attributor &A) 299 : AAAMDAttributes(IRP, A) {} 300 301 void initialize(Attributor &A) override { 302 Function *F = getAssociatedFunction(); 303 CallingConv::ID CC = F->getCallingConv(); 304 bool CallingConvSupportsAllImplicits = (CC != CallingConv::AMDGPU_Gfx); 305 306 // Don't add attributes to instrinsics 307 if (F->isIntrinsic()) { 308 indicatePessimisticFixpoint(); 309 return; 310 } 311 312 // Ignore functions with graphics calling conventions, these are currently 313 // not allowed to have kernel arguments. 314 if (AMDGPU::isGraphics(F->getCallingConv())) { 315 indicatePessimisticFixpoint(); 316 return; 317 } 318 319 for (StringRef Attr : ImplicitAttrNames) { 320 if (F->hasFnAttribute(Attr)) 321 Attributes.insert(Attr); 322 } 323 324 // TODO: We shouldn't need this in the future. 325 if (CallingConvSupportsAllImplicits && 326 F->hasAddressTaken(nullptr, true, true, true)) { 327 for (StringRef AttrName : ImplicitAttrNames) { 328 Attributes.insert(AttrName); 329 } 330 } 331 } 332 333 ChangeStatus updateImpl(Attributor &A) override { 334 Function *F = getAssociatedFunction(); 335 ChangeStatus Change = ChangeStatus::UNCHANGED; 336 bool IsNonEntryFunc = !AMDGPU::isEntryFunctionCC(F->getCallingConv()); 337 CallingConv::ID CC = F->getCallingConv(); 338 bool CallingConvSupportsAllImplicits = (CC != CallingConv::AMDGPU_Gfx); 339 auto &InfoCache = static_cast<AMDGPUInformationCache &>(A.getInfoCache()); 340 341 auto AddAttribute = [&](StringRef AttrName) { 342 if (Attributes.insert(AttrName).second) 343 Change = ChangeStatus::CHANGED; 344 }; 345 346 // Check for Intrinsics and propagate attributes. 347 const AACallEdges &AAEdges = A.getAAFor<AACallEdges>( 348 *this, this->getIRPosition(), DepClassTy::REQUIRED); 349 350 // We have to assume that we can reach a function with these attributes. 351 // We do not consider inline assembly as a unknown callee. 352 if (CallingConvSupportsAllImplicits && AAEdges.hasNonAsmUnknownCallee()) { 353 for (StringRef AttrName : ImplicitAttrNames) { 354 AddAttribute(AttrName); 355 } 356 } 357 358 bool NeedsQueuePtr = false; 359 bool HasCall = false; 360 for (Function *Callee : AAEdges.getOptimisticEdges()) { 361 Intrinsic::ID IID = Callee->getIntrinsicID(); 362 if (IID != Intrinsic::not_intrinsic) { 363 if (!IsNonEntryFunc && IID == Intrinsic::amdgcn_kernarg_segment_ptr) { 364 AddAttribute("amdgpu-kernarg-segment-ptr"); 365 continue; 366 } 367 368 bool NonKernelOnly = false; 369 StringRef AttrName = 370 intrinsicToAttrName(IID, NonKernelOnly, NeedsQueuePtr); 371 372 if (!AttrName.empty() && (IsNonEntryFunc || !NonKernelOnly)) 373 AddAttribute(AttrName); 374 375 continue; 376 } 377 378 HasCall = true; 379 const AAAMDAttributes &AAAMD = A.getAAFor<AAAMDAttributes>( 380 *this, IRPosition::function(*Callee), DepClassTy::REQUIRED); 381 const DenseSet<StringRef> &CalleeAttributes = AAAMD.getAttributes(); 382 // Propagate implicit attributes from called function. 383 for (StringRef AttrName : ImplicitAttrNames) 384 if (CalleeAttributes.count(AttrName)) 385 AddAttribute(AttrName); 386 } 387 388 HasCall |= AAEdges.hasUnknownCallee(); 389 if (!IsNonEntryFunc && HasCall) 390 AddAttribute("amdgpu-calls"); 391 392 // Check the function body. 393 auto CheckAlloca = [&](Instruction &I) { 394 AddAttribute("amdgpu-stack-objects"); 395 return false; 396 }; 397 398 bool UsedAssumedInformation = false; 399 A.checkForAllInstructions(CheckAlloca, *this, {Instruction::Alloca}, 400 UsedAssumedInformation); 401 402 // If we found that we need amdgpu-queue-ptr, nothing else to do. 403 if (NeedsQueuePtr || Attributes.count("amdgpu-queue-ptr")) { 404 AddAttribute("amdgpu-queue-ptr"); 405 return Change; 406 } 407 408 auto CheckAddrSpaceCasts = [&](Instruction &I) { 409 unsigned SrcAS = static_cast<AddrSpaceCastInst &>(I).getSrcAddressSpace(); 410 if (castRequiresQueuePtr(SrcAS)) { 411 NeedsQueuePtr = true; 412 return false; 413 } 414 return true; 415 }; 416 417 bool HasApertureRegs = InfoCache.hasApertureRegs(*F); 418 419 // `checkForAllInstructions` is much more cheaper than going through all 420 // instructions, try it first. 421 422 // amdgpu-queue-ptr is not needed if aperture regs is present. 423 if (!HasApertureRegs) 424 A.checkForAllInstructions(CheckAddrSpaceCasts, *this, 425 {Instruction::AddrSpaceCast}, 426 UsedAssumedInformation); 427 428 // If we found that we need amdgpu-queue-ptr, nothing else to do. 429 if (NeedsQueuePtr) { 430 AddAttribute("amdgpu-queue-ptr"); 431 return Change; 432 } 433 434 if (!IsNonEntryFunc && HasApertureRegs) 435 return Change; 436 437 for (BasicBlock &BB : *F) { 438 for (Instruction &I : BB) { 439 for (const Use &U : I.operands()) { 440 if (const auto *C = dyn_cast<Constant>(U)) { 441 if (InfoCache.needsQueuePtr(C, *F)) { 442 AddAttribute("amdgpu-queue-ptr"); 443 return Change; 444 } 445 } 446 } 447 } 448 } 449 450 return Change; 451 } 452 453 ChangeStatus manifest(Attributor &A) override { 454 SmallVector<Attribute, 8> AttrList; 455 LLVMContext &Ctx = getAssociatedFunction()->getContext(); 456 457 for (StringRef AttrName : Attributes) 458 AttrList.push_back(Attribute::get(Ctx, AttrName)); 459 460 return IRAttributeManifest::manifestAttrs(A, getIRPosition(), AttrList, 461 /* ForceReplace */ true); 462 } 463 464 const std::string getAsStr() const override { 465 return "AMDInfo[" + std::to_string(Attributes.size()) + "]"; 466 } 467 468 const DenseSet<StringRef> &getAttributes() const override { 469 return Attributes; 470 } 471 472 /// See AbstractAttribute::trackStatistics() 473 void trackStatistics() const override {} 474 475 private: 476 DenseSet<StringRef> Attributes; 477 }; 478 479 AAAMDAttributes &AAAMDAttributes::createForPosition(const IRPosition &IRP, 480 Attributor &A) { 481 if (IRP.getPositionKind() == IRPosition::IRP_FUNCTION) 482 return *new (A.Allocator) AAAMDAttributesFunction(IRP, A); 483 llvm_unreachable("AAAMDAttributes is only valid for function position"); 484 } 485 486 class AMDGPUAttributor : public ModulePass { 487 public: 488 AMDGPUAttributor() : ModulePass(ID) {} 489 490 /// doInitialization - Virtual method overridden by subclasses to do 491 /// any necessary initialization before any pass is run. 492 bool doInitialization(Module &) override { 493 auto *TPC = getAnalysisIfAvailable<TargetPassConfig>(); 494 if (!TPC) 495 report_fatal_error("TargetMachine is required"); 496 497 TM = &TPC->getTM<TargetMachine>(); 498 return false; 499 } 500 501 bool runOnModule(Module &M) override { 502 SetVector<Function *> Functions; 503 AnalysisGetter AG; 504 for (Function &F : M) 505 Functions.insert(&F); 506 507 CallGraphUpdater CGUpdater; 508 BumpPtrAllocator Allocator; 509 AMDGPUInformationCache InfoCache(M, AG, Allocator, nullptr, *TM); 510 Attributor A(Functions, InfoCache, CGUpdater); 511 512 for (Function &F : M) { 513 A.getOrCreateAAFor<AAAMDAttributes>(IRPosition::function(F)); 514 A.getOrCreateAAFor<AAAMDWorkGroupSize>(IRPosition::function(F)); 515 } 516 517 ChangeStatus Change = A.run(); 518 return Change == ChangeStatus::CHANGED; 519 } 520 521 StringRef getPassName() const override { return "AMDGPU Attributor"; } 522 TargetMachine *TM; 523 static char ID; 524 }; 525 526 char AMDGPUAttributor::ID = 0; 527 528 Pass *llvm::createAMDGPUAttributorPass() { return new AMDGPUAttributor(); } 529 INITIALIZE_PASS(AMDGPUAttributor, DEBUG_TYPE, "AMDGPU Attributor", false, false) 530