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