1 //===----- SVEIntrinsicOpts - SVE ACLE Intrinsics Opts --------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 // Performs general IR level optimizations on SVE intrinsics.
11 //
12 // This pass performs the following optimizations:
13 //
14 // - removes unnecessary ptrue intrinsics (llvm.aarch64.sve.ptrue), e.g:
15 //     %1 = @llvm.aarch64.sve.ptrue.nxv4i1(i32 31)
16 //     %2 = @llvm.aarch64.sve.ptrue.nxv8i1(i32 31)
17 //     ; (%1 can be replaced with a reinterpret of %2)
18 //
19 // - optimizes ptest intrinsics where the operands are being needlessly
20 //   converted to and from svbool_t.
21 //
22 //===----------------------------------------------------------------------===//
23 
24 #include "AArch64.h"
25 #include "Utils/AArch64BaseInfo.h"
26 #include "llvm/ADT/PostOrderIterator.h"
27 #include "llvm/ADT/SetVector.h"
28 #include "llvm/IR/Constants.h"
29 #include "llvm/IR/Dominators.h"
30 #include "llvm/IR/IRBuilder.h"
31 #include "llvm/IR/Instructions.h"
32 #include "llvm/IR/IntrinsicInst.h"
33 #include "llvm/IR/IntrinsicsAArch64.h"
34 #include "llvm/IR/LLVMContext.h"
35 #include "llvm/IR/PatternMatch.h"
36 #include "llvm/InitializePasses.h"
37 #include "llvm/Support/Debug.h"
38 
39 using namespace llvm;
40 using namespace llvm::PatternMatch;
41 
42 #define DEBUG_TYPE "aarch64-sve-intrinsic-opts"
43 
44 namespace llvm {
45 void initializeSVEIntrinsicOptsPass(PassRegistry &);
46 }
47 
48 namespace {
49 struct SVEIntrinsicOpts : public ModulePass {
50   static char ID; // Pass identification, replacement for typeid
51   SVEIntrinsicOpts() : ModulePass(ID) {
52     initializeSVEIntrinsicOptsPass(*PassRegistry::getPassRegistry());
53   }
54 
55   bool runOnModule(Module &M) override;
56   void getAnalysisUsage(AnalysisUsage &AU) const override;
57 
58 private:
59   bool coalescePTrueIntrinsicCalls(BasicBlock &BB,
60                                    SmallSetVector<IntrinsicInst *, 4> &PTrues);
61   bool optimizePTrueIntrinsicCalls(SmallSetVector<Function *, 4> &Functions);
62   bool optimizePredicateStore(Instruction *I);
63   bool optimizePredicateLoad(Instruction *I);
64 
65   bool optimizeInstructions(SmallSetVector<Function *, 4> &Functions);
66 
67   /// Operates at the function-scope. I.e., optimizations are applied local to
68   /// the functions themselves.
69   bool optimizeFunctions(SmallSetVector<Function *, 4> &Functions);
70 };
71 } // end anonymous namespace
72 
73 void SVEIntrinsicOpts::getAnalysisUsage(AnalysisUsage &AU) const {
74   AU.addRequired<DominatorTreeWrapperPass>();
75   AU.setPreservesCFG();
76 }
77 
78 char SVEIntrinsicOpts::ID = 0;
79 static const char *name = "SVE intrinsics optimizations";
80 INITIALIZE_PASS_BEGIN(SVEIntrinsicOpts, DEBUG_TYPE, name, false, false)
81 INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass);
82 INITIALIZE_PASS_END(SVEIntrinsicOpts, DEBUG_TYPE, name, false, false)
83 
84 ModulePass *llvm::createSVEIntrinsicOptsPass() {
85   return new SVEIntrinsicOpts();
86 }
87 
88 /// Checks if a ptrue intrinsic call is promoted. The act of promoting a
89 /// ptrue will introduce zeroing. For example:
90 ///
91 ///     %1 = <vscale x 4 x i1> call @llvm.aarch64.sve.ptrue.nxv4i1(i32 31)
92 ///     %2 = <vscale x 16 x i1> call @llvm.aarch64.sve.convert.to.svbool.nxv4i1(<vscale x 4 x i1> %1)
93 ///     %3 = <vscale x 8 x i1> call @llvm.aarch64.sve.convert.from.svbool.nxv8i1(<vscale x 16 x i1> %2)
94 ///
95 /// %1 is promoted, because it is converted:
96 ///
97 ///     <vscale x 4 x i1> => <vscale x 16 x i1> => <vscale x 8 x i1>
98 ///
99 /// via a sequence of the SVE reinterpret intrinsics convert.{to,from}.svbool.
100 static bool isPTruePromoted(IntrinsicInst *PTrue) {
101   // Find all users of this intrinsic that are calls to convert-to-svbool
102   // reinterpret intrinsics.
103   SmallVector<IntrinsicInst *, 4> ConvertToUses;
104   for (User *User : PTrue->users()) {
105     if (match(User, m_Intrinsic<Intrinsic::aarch64_sve_convert_to_svbool>())) {
106       ConvertToUses.push_back(cast<IntrinsicInst>(User));
107     }
108   }
109 
110   // If no such calls were found, this is ptrue is not promoted.
111   if (ConvertToUses.empty())
112     return false;
113 
114   // Otherwise, try to find users of the convert-to-svbool intrinsics that are
115   // calls to the convert-from-svbool intrinsic, and would result in some lanes
116   // being zeroed.
117   const auto *PTrueVTy = cast<ScalableVectorType>(PTrue->getType());
118   for (IntrinsicInst *ConvertToUse : ConvertToUses) {
119     for (User *User : ConvertToUse->users()) {
120       auto *IntrUser = dyn_cast<IntrinsicInst>(User);
121       if (IntrUser && IntrUser->getIntrinsicID() ==
122                           Intrinsic::aarch64_sve_convert_from_svbool) {
123         const auto *IntrUserVTy = cast<ScalableVectorType>(IntrUser->getType());
124 
125         // Would some lanes become zeroed by the conversion?
126         if (IntrUserVTy->getElementCount().getKnownMinValue() >
127             PTrueVTy->getElementCount().getKnownMinValue())
128           // This is a promoted ptrue.
129           return true;
130       }
131     }
132   }
133 
134   // If no matching calls were found, this is not a promoted ptrue.
135   return false;
136 }
137 
138 /// Attempts to coalesce ptrues in a basic block.
139 bool SVEIntrinsicOpts::coalescePTrueIntrinsicCalls(
140     BasicBlock &BB, SmallSetVector<IntrinsicInst *, 4> &PTrues) {
141   if (PTrues.size() <= 1)
142     return false;
143 
144   // Find the ptrue with the most lanes.
145   auto *MostEncompassingPTrue = *std::max_element(
146       PTrues.begin(), PTrues.end(), [](auto *PTrue1, auto *PTrue2) {
147         auto *PTrue1VTy = cast<ScalableVectorType>(PTrue1->getType());
148         auto *PTrue2VTy = cast<ScalableVectorType>(PTrue2->getType());
149         return PTrue1VTy->getElementCount().getKnownMinValue() <
150                PTrue2VTy->getElementCount().getKnownMinValue();
151       });
152 
153   // Remove the most encompassing ptrue, as well as any promoted ptrues, leaving
154   // behind only the ptrues to be coalesced.
155   PTrues.remove(MostEncompassingPTrue);
156   PTrues.remove_if([](auto *PTrue) { return isPTruePromoted(PTrue); });
157 
158   // Hoist MostEncompassingPTrue to the start of the basic block. It is always
159   // safe to do this, since ptrue intrinsic calls are guaranteed to have no
160   // predecessors.
161   MostEncompassingPTrue->moveBefore(BB, BB.getFirstInsertionPt());
162 
163   LLVMContext &Ctx = BB.getContext();
164   IRBuilder<> Builder(Ctx);
165   Builder.SetInsertPoint(&BB, ++MostEncompassingPTrue->getIterator());
166 
167   auto *MostEncompassingPTrueVTy =
168       cast<VectorType>(MostEncompassingPTrue->getType());
169   auto *ConvertToSVBool = Builder.CreateIntrinsic(
170       Intrinsic::aarch64_sve_convert_to_svbool, {MostEncompassingPTrueVTy},
171       {MostEncompassingPTrue});
172 
173   bool ConvertFromCreated = false;
174   for (auto *PTrue : PTrues) {
175     auto *PTrueVTy = cast<VectorType>(PTrue->getType());
176 
177     // Only create the converts if the types are not already the same, otherwise
178     // just use the most encompassing ptrue.
179     if (MostEncompassingPTrueVTy != PTrueVTy) {
180       ConvertFromCreated = true;
181 
182       Builder.SetInsertPoint(&BB, ++ConvertToSVBool->getIterator());
183       auto *ConvertFromSVBool =
184           Builder.CreateIntrinsic(Intrinsic::aarch64_sve_convert_from_svbool,
185                                   {PTrueVTy}, {ConvertToSVBool});
186       PTrue->replaceAllUsesWith(ConvertFromSVBool);
187     } else
188       PTrue->replaceAllUsesWith(MostEncompassingPTrue);
189 
190     PTrue->eraseFromParent();
191   }
192 
193   // We never used the ConvertTo so remove it
194   if (!ConvertFromCreated)
195     ConvertToSVBool->eraseFromParent();
196 
197   return true;
198 }
199 
200 /// The goal of this function is to remove redundant calls to the SVE ptrue
201 /// intrinsic in each basic block within the given functions.
202 ///
203 /// SVE ptrues have two representations in LLVM IR:
204 /// - a logical representation -- an arbitrary-width scalable vector of i1s,
205 ///   i.e. <vscale x N x i1>.
206 /// - a physical representation (svbool, <vscale x 16 x i1>) -- a 16-element
207 ///   scalable vector of i1s, i.e. <vscale x 16 x i1>.
208 ///
209 /// The SVE ptrue intrinsic is used to create a logical representation of an SVE
210 /// predicate. Suppose that we have two SVE ptrue intrinsic calls: P1 and P2. If
211 /// P1 creates a logical SVE predicate that is at least as wide as the logical
212 /// SVE predicate created by P2, then all of the bits that are true in the
213 /// physical representation of P2 are necessarily also true in the physical
214 /// representation of P1. P1 'encompasses' P2, therefore, the intrinsic call to
215 /// P2 is redundant and can be replaced by an SVE reinterpret of P1 via
216 /// convert.{to,from}.svbool.
217 ///
218 /// Currently, this pass only coalesces calls to SVE ptrue intrinsics
219 /// if they match the following conditions:
220 ///
221 /// - the call to the intrinsic uses either the SV_ALL or SV_POW2 patterns.
222 ///   SV_ALL indicates that all bits of the predicate vector are to be set to
223 ///   true. SV_POW2 indicates that all bits of the predicate vector up to the
224 ///   largest power-of-two are to be set to true.
225 /// - the result of the call to the intrinsic is not promoted to a wider
226 ///   predicate. In this case, keeping the extra ptrue leads to better codegen
227 ///   -- coalescing here would create an irreducible chain of SVE reinterprets
228 ///   via convert.{to,from}.svbool.
229 ///
230 /// EXAMPLE:
231 ///
232 ///     %1 = <vscale x 8 x i1> ptrue(i32 SV_ALL)
233 ///     ; Logical:  <1, 1, 1, 1, 1, 1, 1, 1>
234 ///     ; Physical: <1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0>
235 ///     ...
236 ///
237 ///     %2 = <vscale x 4 x i1> ptrue(i32 SV_ALL)
238 ///     ; Logical:  <1, 1, 1, 1>
239 ///     ; Physical: <1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0>
240 ///     ...
241 ///
242 /// Here, %2 can be replaced by an SVE reinterpret of %1, giving, for instance:
243 ///
244 ///     %1 = <vscale x 8 x i1> ptrue(i32 i31)
245 ///     %2 = <vscale x 16 x i1> convert.to.svbool(<vscale x 8 x i1> %1)
246 ///     %3 = <vscale x 4 x i1> convert.from.svbool(<vscale x 16 x i1> %2)
247 ///
248 bool SVEIntrinsicOpts::optimizePTrueIntrinsicCalls(
249     SmallSetVector<Function *, 4> &Functions) {
250   bool Changed = false;
251 
252   for (auto *F : Functions) {
253     for (auto &BB : *F) {
254       SmallSetVector<IntrinsicInst *, 4> SVAllPTrues;
255       SmallSetVector<IntrinsicInst *, 4> SVPow2PTrues;
256 
257       // For each basic block, collect the used ptrues and try to coalesce them.
258       for (Instruction &I : BB) {
259         if (I.use_empty())
260           continue;
261 
262         auto *IntrI = dyn_cast<IntrinsicInst>(&I);
263         if (!IntrI || IntrI->getIntrinsicID() != Intrinsic::aarch64_sve_ptrue)
264           continue;
265 
266         const auto PTruePattern =
267             cast<ConstantInt>(IntrI->getOperand(0))->getZExtValue();
268 
269         if (PTruePattern == AArch64SVEPredPattern::all)
270           SVAllPTrues.insert(IntrI);
271         if (PTruePattern == AArch64SVEPredPattern::pow2)
272           SVPow2PTrues.insert(IntrI);
273       }
274 
275       Changed |= coalescePTrueIntrinsicCalls(BB, SVAllPTrues);
276       Changed |= coalescePTrueIntrinsicCalls(BB, SVPow2PTrues);
277     }
278   }
279 
280   return Changed;
281 }
282 
283 // This is done in SVEIntrinsicOpts rather than InstCombine so that we introduce
284 // scalable stores as late as possible
285 bool SVEIntrinsicOpts::optimizePredicateStore(Instruction *I) {
286   auto *F = I->getFunction();
287   auto Attr = F->getFnAttribute(Attribute::VScaleRange);
288   if (!Attr.isValid())
289     return false;
290 
291   unsigned MinVScale, MaxVScale;
292   std::tie(MinVScale, MaxVScale) = Attr.getVScaleRangeArgs();
293   // The transform needs to know the exact runtime length of scalable vectors
294   if (MinVScale != MaxVScale || MinVScale == 0)
295     return false;
296 
297   auto *PredType =
298       ScalableVectorType::get(Type::getInt1Ty(I->getContext()), 16);
299   auto *FixedPredType =
300       FixedVectorType::get(Type::getInt8Ty(I->getContext()), MinVScale * 2);
301 
302   // If we have a store..
303   auto *Store = dyn_cast<StoreInst>(I);
304   if (!Store || !Store->isSimple())
305     return false;
306 
307   // ..that is storing a predicate vector sized worth of bits..
308   if (Store->getOperand(0)->getType() != FixedPredType)
309     return false;
310 
311   // ..where the value stored comes from a vector extract..
312   auto *IntrI = dyn_cast<IntrinsicInst>(Store->getOperand(0));
313   if (!IntrI ||
314       IntrI->getIntrinsicID() != Intrinsic::experimental_vector_extract)
315     return false;
316 
317   // ..that is extracting from index 0..
318   if (!cast<ConstantInt>(IntrI->getOperand(1))->isZero())
319     return false;
320 
321   // ..where the value being extract from comes from a bitcast
322   auto *BitCast = dyn_cast<BitCastInst>(IntrI->getOperand(0));
323   if (!BitCast)
324     return false;
325 
326   // ..and the bitcast is casting from predicate type
327   if (BitCast->getOperand(0)->getType() != PredType)
328     return false;
329 
330   IRBuilder<> Builder(I->getContext());
331   Builder.SetInsertPoint(I);
332 
333   auto *PtrBitCast = Builder.CreateBitCast(
334       Store->getPointerOperand(),
335       PredType->getPointerTo(Store->getPointerAddressSpace()));
336   Builder.CreateStore(BitCast->getOperand(0), PtrBitCast);
337 
338   Store->eraseFromParent();
339   if (IntrI->getNumUses() == 0)
340     IntrI->eraseFromParent();
341   if (BitCast->getNumUses() == 0)
342     BitCast->eraseFromParent();
343 
344   return true;
345 }
346 
347 // This is done in SVEIntrinsicOpts rather than InstCombine so that we introduce
348 // scalable loads as late as possible
349 bool SVEIntrinsicOpts::optimizePredicateLoad(Instruction *I) {
350   auto *F = I->getFunction();
351   auto Attr = F->getFnAttribute(Attribute::VScaleRange);
352   if (!Attr.isValid())
353     return false;
354 
355   unsigned MinVScale, MaxVScale;
356   std::tie(MinVScale, MaxVScale) = Attr.getVScaleRangeArgs();
357   // The transform needs to know the exact runtime length of scalable vectors
358   if (MinVScale != MaxVScale || MinVScale == 0)
359     return false;
360 
361   auto *PredType =
362       ScalableVectorType::get(Type::getInt1Ty(I->getContext()), 16);
363   auto *FixedPredType =
364       FixedVectorType::get(Type::getInt8Ty(I->getContext()), MinVScale * 2);
365 
366   // If we have a bitcast..
367   auto *BitCast = dyn_cast<BitCastInst>(I);
368   if (!BitCast || BitCast->getType() != PredType)
369     return false;
370 
371   // ..whose operand is a vector_insert..
372   auto *IntrI = dyn_cast<IntrinsicInst>(BitCast->getOperand(0));
373   if (!IntrI ||
374       IntrI->getIntrinsicID() != Intrinsic::experimental_vector_insert)
375     return false;
376 
377   // ..that is inserting into index zero of an undef vector..
378   if (!isa<UndefValue>(IntrI->getOperand(0)) ||
379       !cast<ConstantInt>(IntrI->getOperand(2))->isZero())
380     return false;
381 
382   // ..where the value inserted comes from a load..
383   auto *Load = dyn_cast<LoadInst>(IntrI->getOperand(1));
384   if (!Load || !Load->isSimple())
385     return false;
386 
387   // ..that is loading a predicate vector sized worth of bits..
388   if (Load->getType() != FixedPredType)
389     return false;
390 
391   IRBuilder<> Builder(I->getContext());
392   Builder.SetInsertPoint(Load);
393 
394   auto *PtrBitCast = Builder.CreateBitCast(
395       Load->getPointerOperand(),
396       PredType->getPointerTo(Load->getPointerAddressSpace()));
397   auto *LoadPred = Builder.CreateLoad(PredType, PtrBitCast);
398 
399   BitCast->replaceAllUsesWith(LoadPred);
400   BitCast->eraseFromParent();
401   if (IntrI->getNumUses() == 0)
402     IntrI->eraseFromParent();
403   if (Load->getNumUses() == 0)
404     Load->eraseFromParent();
405 
406   return true;
407 }
408 
409 bool SVEIntrinsicOpts::optimizeInstructions(
410     SmallSetVector<Function *, 4> &Functions) {
411   bool Changed = false;
412 
413   for (auto *F : Functions) {
414     DominatorTree *DT = &getAnalysis<DominatorTreeWrapperPass>(*F).getDomTree();
415 
416     // Traverse the DT with an rpo walk so we see defs before uses, allowing
417     // simplification to be done incrementally.
418     BasicBlock *Root = DT->getRoot();
419     ReversePostOrderTraversal<BasicBlock *> RPOT(Root);
420     for (auto *BB : RPOT) {
421       for (Instruction &I : make_early_inc_range(*BB)) {
422         switch (I.getOpcode()) {
423         case Instruction::Store:
424           Changed |= optimizePredicateStore(&I);
425           break;
426         case Instruction::BitCast:
427           Changed |= optimizePredicateLoad(&I);
428           break;
429         }
430       }
431     }
432   }
433 
434   return Changed;
435 }
436 
437 bool SVEIntrinsicOpts::optimizeFunctions(
438     SmallSetVector<Function *, 4> &Functions) {
439   bool Changed = false;
440 
441   Changed |= optimizePTrueIntrinsicCalls(Functions);
442   Changed |= optimizeInstructions(Functions);
443 
444   return Changed;
445 }
446 
447 bool SVEIntrinsicOpts::runOnModule(Module &M) {
448   bool Changed = false;
449   SmallSetVector<Function *, 4> Functions;
450 
451   // Check for SVE intrinsic declarations first so that we only iterate over
452   // relevant functions. Where an appropriate declaration is found, store the
453   // function(s) where it is used so we can target these only.
454   for (auto &F : M.getFunctionList()) {
455     if (!F.isDeclaration())
456       continue;
457 
458     switch (F.getIntrinsicID()) {
459     case Intrinsic::experimental_vector_extract:
460     case Intrinsic::experimental_vector_insert:
461     case Intrinsic::aarch64_sve_ptrue:
462       for (User *U : F.users())
463         Functions.insert(cast<Instruction>(U)->getFunction());
464       break;
465     default:
466       break;
467     }
468   }
469 
470   if (!Functions.empty())
471     Changed |= optimizeFunctions(Functions);
472 
473   return Changed;
474 }
475