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 "Utils/AArch64BaseInfo.h"
25 #include "llvm/ADT/PostOrderIterator.h"
26 #include "llvm/ADT/SetVector.h"
27 #include "llvm/IR/Constants.h"
28 #include "llvm/IR/Dominators.h"
29 #include "llvm/IR/IRBuilder.h"
30 #include "llvm/IR/Instructions.h"
31 #include "llvm/IR/IntrinsicInst.h"
32 #include "llvm/IR/IntrinsicsAArch64.h"
33 #include "llvm/IR/LLVMContext.h"
34 #include "llvm/IR/PatternMatch.h"
35 #include "llvm/InitializePasses.h"
36 #include "llvm/Support/Debug.h"
37 
38 using namespace llvm;
39 using namespace llvm::PatternMatch;
40 
41 #define DEBUG_TYPE "aarch64-sve-intrinsic-opts"
42 
43 namespace llvm {
44 void initializeSVEIntrinsicOptsPass(PassRegistry &);
45 }
46 
47 namespace {
48 struct SVEIntrinsicOpts : public ModulePass {
49   static char ID; // Pass identification, replacement for typeid
50   SVEIntrinsicOpts() : ModulePass(ID) {
51     initializeSVEIntrinsicOptsPass(*PassRegistry::getPassRegistry());
52   }
53 
54   bool runOnModule(Module &M) override;
55   void getAnalysisUsage(AnalysisUsage &AU) const override;
56 
57 private:
58   bool coalescePTrueIntrinsicCalls(BasicBlock &BB,
59                                    SmallSetVector<IntrinsicInst *, 4> &PTrues);
60   bool optimizePTrueIntrinsicCalls(SmallSetVector<Function *, 4> &Functions);
61 
62   /// Operates at the instruction-scope. I.e., optimizations are applied local
63   /// to individual instructions.
64   static bool optimizeIntrinsic(Instruction *I);
65   bool optimizeIntrinsicCalls(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   static bool optimizePTest(IntrinsicInst *I);
72   static bool optimizeVectorMul(IntrinsicInst *I);
73   static bool optimizeTBL(IntrinsicInst *I);
74 };
75 } // end anonymous namespace
76 
77 void SVEIntrinsicOpts::getAnalysisUsage(AnalysisUsage &AU) const {
78   AU.addRequired<DominatorTreeWrapperPass>();
79   AU.setPreservesCFG();
80 }
81 
82 char SVEIntrinsicOpts::ID = 0;
83 static const char *name = "SVE intrinsics optimizations";
84 INITIALIZE_PASS_BEGIN(SVEIntrinsicOpts, DEBUG_TYPE, name, false, false)
85 INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass);
86 INITIALIZE_PASS_END(SVEIntrinsicOpts, DEBUG_TYPE, name, false, false)
87 
88 namespace llvm {
89 ModulePass *createSVEIntrinsicOptsPass() { return new SVEIntrinsicOpts(); }
90 } // namespace llvm
91 
92 /// Checks if a ptrue intrinsic call is promoted. The act of promoting a
93 /// ptrue will introduce zeroing. For example:
94 ///
95 ///     %1 = <vscale x 4 x i1> call @llvm.aarch64.sve.ptrue.nxv4i1(i32 31)
96 ///     %2 = <vscale x 16 x i1> call @llvm.aarch64.sve.convert.to.svbool.nxv4i1(<vscale x 4 x i1> %1)
97 ///     %3 = <vscale x 8 x i1> call @llvm.aarch64.sve.convert.from.svbool.nxv8i1(<vscale x 16 x i1> %2)
98 ///
99 /// %1 is promoted, because it is converted:
100 ///
101 ///     <vscale x 4 x i1> => <vscale x 16 x i1> => <vscale x 8 x i1>
102 ///
103 /// via a sequence of the SVE reinterpret intrinsics convert.{to,from}.svbool.
104 bool isPTruePromoted(IntrinsicInst *PTrue) {
105   // Find all users of this intrinsic that are calls to convert-to-svbool
106   // reinterpret intrinsics.
107   SmallVector<IntrinsicInst *, 4> ConvertToUses;
108   for (User *User : PTrue->users()) {
109     if (match(User, m_Intrinsic<Intrinsic::aarch64_sve_convert_to_svbool>())) {
110       ConvertToUses.push_back(cast<IntrinsicInst>(User));
111     }
112   }
113 
114   // If no such calls were found, this is ptrue is not promoted.
115   if (ConvertToUses.empty())
116     return false;
117 
118   // Otherwise, try to find users of the convert-to-svbool intrinsics that are
119   // calls to the convert-from-svbool intrinsic, and would result in some lanes
120   // being zeroed.
121   const auto *PTrueVTy = cast<ScalableVectorType>(PTrue->getType());
122   for (IntrinsicInst *ConvertToUse : ConvertToUses) {
123     for (User *User : ConvertToUse->users()) {
124       auto *IntrUser = dyn_cast<IntrinsicInst>(User);
125       if (IntrUser && IntrUser->getIntrinsicID() ==
126                           Intrinsic::aarch64_sve_convert_from_svbool) {
127         const auto *IntrUserVTy = cast<ScalableVectorType>(IntrUser->getType());
128 
129         // Would some lanes become zeroed by the conversion?
130         if (IntrUserVTy->getElementCount().getKnownMinValue() >
131             PTrueVTy->getElementCount().getKnownMinValue())
132           // This is a promoted ptrue.
133           return true;
134       }
135     }
136   }
137 
138   // If no matching calls were found, this is not a promoted ptrue.
139   return false;
140 }
141 
142 /// Attempts to coalesce ptrues in a basic block.
143 bool SVEIntrinsicOpts::coalescePTrueIntrinsicCalls(
144     BasicBlock &BB, SmallSetVector<IntrinsicInst *, 4> &PTrues) {
145   if (PTrues.size() <= 1)
146     return false;
147 
148   // Find the ptrue with the most lanes.
149   auto *MostEncompassingPTrue = *std::max_element(
150       PTrues.begin(), PTrues.end(), [](auto *PTrue1, auto *PTrue2) {
151         auto *PTrue1VTy = cast<ScalableVectorType>(PTrue1->getType());
152         auto *PTrue2VTy = cast<ScalableVectorType>(PTrue2->getType());
153         return PTrue1VTy->getElementCount().getKnownMinValue() <
154                PTrue2VTy->getElementCount().getKnownMinValue();
155       });
156 
157   // Remove the most encompassing ptrue, as well as any promoted ptrues, leaving
158   // behind only the ptrues to be coalesced.
159   PTrues.remove(MostEncompassingPTrue);
160   PTrues.remove_if([](auto *PTrue) { return isPTruePromoted(PTrue); });
161 
162   // Hoist MostEncompassingPTrue to the start of the basic block. It is always
163   // safe to do this, since ptrue intrinsic calls are guaranteed to have no
164   // predecessors.
165   MostEncompassingPTrue->moveBefore(BB, BB.getFirstInsertionPt());
166 
167   LLVMContext &Ctx = BB.getContext();
168   IRBuilder<> Builder(Ctx);
169   Builder.SetInsertPoint(&BB, ++MostEncompassingPTrue->getIterator());
170 
171   auto *MostEncompassingPTrueVTy =
172       cast<VectorType>(MostEncompassingPTrue->getType());
173   auto *ConvertToSVBool = Builder.CreateIntrinsic(
174       Intrinsic::aarch64_sve_convert_to_svbool, {MostEncompassingPTrueVTy},
175       {MostEncompassingPTrue});
176 
177   bool ConvertFromCreated = false;
178   for (auto *PTrue : PTrues) {
179     auto *PTrueVTy = cast<VectorType>(PTrue->getType());
180 
181     // Only create the converts if the types are not already the same, otherwise
182     // just use the most encompassing ptrue.
183     if (MostEncompassingPTrueVTy != PTrueVTy) {
184       ConvertFromCreated = true;
185 
186       Builder.SetInsertPoint(&BB, ++ConvertToSVBool->getIterator());
187       auto *ConvertFromSVBool =
188           Builder.CreateIntrinsic(Intrinsic::aarch64_sve_convert_from_svbool,
189                                   {PTrueVTy}, {ConvertToSVBool});
190       PTrue->replaceAllUsesWith(ConvertFromSVBool);
191     } else
192       PTrue->replaceAllUsesWith(MostEncompassingPTrue);
193 
194     PTrue->eraseFromParent();
195   }
196 
197   // We never used the ConvertTo so remove it
198   if (!ConvertFromCreated)
199     ConvertToSVBool->eraseFromParent();
200 
201   return true;
202 }
203 
204 /// The goal of this function is to remove redundant calls to the SVE ptrue
205 /// intrinsic in each basic block within the given functions.
206 ///
207 /// SVE ptrues have two representations in LLVM IR:
208 /// - a logical representation -- an arbitrary-width scalable vector of i1s,
209 ///   i.e. <vscale x N x i1>.
210 /// - a physical representation (svbool, <vscale x 16 x i1>) -- a 16-element
211 ///   scalable vector of i1s, i.e. <vscale x 16 x i1>.
212 ///
213 /// The SVE ptrue intrinsic is used to create a logical representation of an SVE
214 /// predicate. Suppose that we have two SVE ptrue intrinsic calls: P1 and P2. If
215 /// P1 creates a logical SVE predicate that is at least as wide as the logical
216 /// SVE predicate created by P2, then all of the bits that are true in the
217 /// physical representation of P2 are necessarily also true in the physical
218 /// representation of P1. P1 'encompasses' P2, therefore, the intrinsic call to
219 /// P2 is redundant and can be replaced by an SVE reinterpret of P1 via
220 /// convert.{to,from}.svbool.
221 ///
222 /// Currently, this pass only coalesces calls to SVE ptrue intrinsics
223 /// if they match the following conditions:
224 ///
225 /// - the call to the intrinsic uses either the SV_ALL or SV_POW2 patterns.
226 ///   SV_ALL indicates that all bits of the predicate vector are to be set to
227 ///   true. SV_POW2 indicates that all bits of the predicate vector up to the
228 ///   largest power-of-two are to be set to true.
229 /// - the result of the call to the intrinsic is not promoted to a wider
230 ///   predicate. In this case, keeping the extra ptrue leads to better codegen
231 ///   -- coalescing here would create an irreducible chain of SVE reinterprets
232 ///   via convert.{to,from}.svbool.
233 ///
234 /// EXAMPLE:
235 ///
236 ///     %1 = <vscale x 8 x i1> ptrue(i32 SV_ALL)
237 ///     ; Logical:  <1, 1, 1, 1, 1, 1, 1, 1>
238 ///     ; Physical: <1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0>
239 ///     ...
240 ///
241 ///     %2 = <vscale x 4 x i1> ptrue(i32 SV_ALL)
242 ///     ; Logical:  <1, 1, 1, 1>
243 ///     ; Physical: <1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0>
244 ///     ...
245 ///
246 /// Here, %2 can be replaced by an SVE reinterpret of %1, giving, for instance:
247 ///
248 ///     %1 = <vscale x 8 x i1> ptrue(i32 i31)
249 ///     %2 = <vscale x 16 x i1> convert.to.svbool(<vscale x 8 x i1> %1)
250 ///     %3 = <vscale x 4 x i1> convert.from.svbool(<vscale x 16 x i1> %2)
251 ///
252 bool SVEIntrinsicOpts::optimizePTrueIntrinsicCalls(
253     SmallSetVector<Function *, 4> &Functions) {
254   bool Changed = false;
255 
256   for (auto *F : Functions) {
257     for (auto &BB : *F) {
258       SmallSetVector<IntrinsicInst *, 4> SVAllPTrues;
259       SmallSetVector<IntrinsicInst *, 4> SVPow2PTrues;
260 
261       // For each basic block, collect the used ptrues and try to coalesce them.
262       for (Instruction &I : BB) {
263         if (I.use_empty())
264           continue;
265 
266         auto *IntrI = dyn_cast<IntrinsicInst>(&I);
267         if (!IntrI || IntrI->getIntrinsicID() != Intrinsic::aarch64_sve_ptrue)
268           continue;
269 
270         const auto PTruePattern =
271             cast<ConstantInt>(IntrI->getOperand(0))->getZExtValue();
272 
273         if (PTruePattern == AArch64SVEPredPattern::all)
274           SVAllPTrues.insert(IntrI);
275         if (PTruePattern == AArch64SVEPredPattern::pow2)
276           SVPow2PTrues.insert(IntrI);
277       }
278 
279       Changed |= coalescePTrueIntrinsicCalls(BB, SVAllPTrues);
280       Changed |= coalescePTrueIntrinsicCalls(BB, SVPow2PTrues);
281     }
282   }
283 
284   return Changed;
285 }
286 
287 bool SVEIntrinsicOpts::optimizePTest(IntrinsicInst *I) {
288   IntrinsicInst *Op1 = dyn_cast<IntrinsicInst>(I->getArgOperand(0));
289   IntrinsicInst *Op2 = dyn_cast<IntrinsicInst>(I->getArgOperand(1));
290 
291   if (Op1 && Op2 &&
292       Op1->getIntrinsicID() == Intrinsic::aarch64_sve_convert_to_svbool &&
293       Op2->getIntrinsicID() == Intrinsic::aarch64_sve_convert_to_svbool &&
294       Op1->getArgOperand(0)->getType() == Op2->getArgOperand(0)->getType()) {
295 
296     Value *Ops[] = {Op1->getArgOperand(0), Op2->getArgOperand(0)};
297     Type *Tys[] = {Op1->getArgOperand(0)->getType()};
298     Module *M = I->getParent()->getParent()->getParent();
299 
300     auto Fn = Intrinsic::getDeclaration(M, I->getIntrinsicID(), Tys);
301     auto CI = CallInst::Create(Fn, Ops, I->getName(), I);
302 
303     I->replaceAllUsesWith(CI);
304     I->eraseFromParent();
305     if (Op1->use_empty())
306       Op1->eraseFromParent();
307     if (Op1 != Op2 && Op2->use_empty())
308       Op2->eraseFromParent();
309 
310     return true;
311   }
312 
313   return false;
314 }
315 
316 bool SVEIntrinsicOpts::optimizeVectorMul(IntrinsicInst *I) {
317   assert((I->getIntrinsicID() == Intrinsic::aarch64_sve_mul ||
318           I->getIntrinsicID() == Intrinsic::aarch64_sve_fmul) &&
319          "Unexpected opcode");
320 
321   auto *OpPredicate = I->getOperand(0);
322   auto *OpMultiplicand = I->getOperand(1);
323   auto *OpMultiplier = I->getOperand(2);
324 
325   // Return true if a given instruction is an aarch64_sve_dup_x intrinsic call
326   // with a unit splat value, false otherwise.
327   auto IsUnitDupX = [](auto *I) {
328     auto *IntrI = dyn_cast<IntrinsicInst>(I);
329     if (!IntrI || IntrI->getIntrinsicID() != Intrinsic::aarch64_sve_dup_x)
330       return false;
331 
332     auto *SplatValue = IntrI->getOperand(0);
333     return match(SplatValue, m_FPOne()) || match(SplatValue, m_One());
334   };
335 
336   // Return true if a given instruction is an aarch64_sve_dup intrinsic call
337   // with a unit splat value, false otherwise.
338   auto IsUnitDup = [](auto *I) {
339     auto *IntrI = dyn_cast<IntrinsicInst>(I);
340     if (!IntrI || IntrI->getIntrinsicID() != Intrinsic::aarch64_sve_dup)
341       return false;
342 
343     auto *SplatValue = IntrI->getOperand(2);
344     return match(SplatValue, m_FPOne()) || match(SplatValue, m_One());
345   };
346 
347   bool Changed = true;
348 
349   // The OpMultiplier variable should always point to the dup (if any), so
350   // swap if necessary.
351   if (IsUnitDup(OpMultiplicand) || IsUnitDupX(OpMultiplicand))
352     std::swap(OpMultiplier, OpMultiplicand);
353 
354   if (IsUnitDupX(OpMultiplier)) {
355     // [f]mul pg (dupx 1) %n => %n
356     I->replaceAllUsesWith(OpMultiplicand);
357     I->eraseFromParent();
358     Changed = true;
359   } else if (IsUnitDup(OpMultiplier)) {
360     // [f]mul pg (dup pg 1) %n => %n
361     auto *DupInst = cast<IntrinsicInst>(OpMultiplier);
362     auto *DupPg = DupInst->getOperand(1);
363     // TODO: this is naive. The optimization is still valid if DupPg
364     // 'encompasses' OpPredicate, not only if they're the same predicate.
365     if (OpPredicate == DupPg) {
366       I->replaceAllUsesWith(OpMultiplicand);
367       I->eraseFromParent();
368       Changed = true;
369     }
370   }
371 
372   // If an instruction was optimized out then it is possible that some dangling
373   // instructions are left.
374   if (Changed) {
375     auto *OpPredicateInst = dyn_cast<Instruction>(OpPredicate);
376     auto *OpMultiplierInst = dyn_cast<Instruction>(OpMultiplier);
377     if (OpMultiplierInst && OpMultiplierInst->use_empty())
378       OpMultiplierInst->eraseFromParent();
379     if (OpPredicateInst && OpPredicateInst->use_empty())
380       OpPredicateInst->eraseFromParent();
381   }
382 
383   return Changed;
384 }
385 
386 bool SVEIntrinsicOpts::optimizeTBL(IntrinsicInst *I) {
387   assert(I->getIntrinsicID() == Intrinsic::aarch64_sve_tbl &&
388          "Unexpected opcode");
389 
390   auto *OpVal = I->getOperand(0);
391   auto *OpIndices = I->getOperand(1);
392   VectorType *VTy = cast<VectorType>(I->getType());
393 
394   // Check whether OpIndices is an aarch64_sve_dup_x intrinsic call with
395   // constant splat value < minimal element count of result.
396   auto *DupXIntrI = dyn_cast<IntrinsicInst>(OpIndices);
397   if (!DupXIntrI || DupXIntrI->getIntrinsicID() != Intrinsic::aarch64_sve_dup_x)
398     return false;
399 
400   auto *SplatValue = dyn_cast<ConstantInt>(DupXIntrI->getOperand(0));
401   if (!SplatValue ||
402       SplatValue->getValue().uge(VTy->getElementCount().getKnownMinValue()))
403     return false;
404 
405   // Convert sve_tbl(OpVal sve_dup_x(SplatValue)) to
406   // splat_vector(extractelement(OpVal, SplatValue)) for further optimization.
407   LLVMContext &Ctx = I->getContext();
408   IRBuilder<> Builder(Ctx);
409   Builder.SetInsertPoint(I);
410   auto *Extract = Builder.CreateExtractElement(OpVal, SplatValue);
411   auto *VectorSplat =
412       Builder.CreateVectorSplat(VTy->getElementCount(), Extract);
413 
414   I->replaceAllUsesWith(VectorSplat);
415   I->eraseFromParent();
416   if (DupXIntrI->use_empty())
417     DupXIntrI->eraseFromParent();
418   return true;
419 }
420 
421 bool SVEIntrinsicOpts::optimizeIntrinsic(Instruction *I) {
422   IntrinsicInst *IntrI = dyn_cast<IntrinsicInst>(I);
423   if (!IntrI)
424     return false;
425 
426   switch (IntrI->getIntrinsicID()) {
427   case Intrinsic::aarch64_sve_fmul:
428   case Intrinsic::aarch64_sve_mul:
429     return optimizeVectorMul(IntrI);
430   case Intrinsic::aarch64_sve_ptest_any:
431   case Intrinsic::aarch64_sve_ptest_first:
432   case Intrinsic::aarch64_sve_ptest_last:
433     return optimizePTest(IntrI);
434   case Intrinsic::aarch64_sve_tbl:
435     return optimizeTBL(IntrI);
436   default:
437     return false;
438   }
439 
440   return true;
441 }
442 
443 bool SVEIntrinsicOpts::optimizeIntrinsicCalls(
444     SmallSetVector<Function *, 4> &Functions) {
445   bool Changed = false;
446   for (auto *F : Functions) {
447     DominatorTree *DT = &getAnalysis<DominatorTreeWrapperPass>(*F).getDomTree();
448 
449     // Traverse the DT with an rpo walk so we see defs before uses, allowing
450     // simplification to be done incrementally.
451     BasicBlock *Root = DT->getRoot();
452     ReversePostOrderTraversal<BasicBlock *> RPOT(Root);
453     for (auto *BB : RPOT)
454       for (Instruction &I : make_early_inc_range(*BB))
455         Changed |= optimizeIntrinsic(&I);
456   }
457   return Changed;
458 }
459 
460 bool SVEIntrinsicOpts::optimizeFunctions(
461     SmallSetVector<Function *, 4> &Functions) {
462   bool Changed = false;
463 
464   Changed |= optimizePTrueIntrinsicCalls(Functions);
465   Changed |= optimizeIntrinsicCalls(Functions);
466 
467   return Changed;
468 }
469 
470 bool SVEIntrinsicOpts::runOnModule(Module &M) {
471   bool Changed = false;
472   SmallSetVector<Function *, 4> Functions;
473 
474   // Check for SVE intrinsic declarations first so that we only iterate over
475   // relevant functions. Where an appropriate declaration is found, store the
476   // function(s) where it is used so we can target these only.
477   for (auto &F : M.getFunctionList()) {
478     if (!F.isDeclaration())
479       continue;
480 
481     switch (F.getIntrinsicID()) {
482     case Intrinsic::aarch64_sve_ptest_any:
483     case Intrinsic::aarch64_sve_ptest_first:
484     case Intrinsic::aarch64_sve_ptest_last:
485     case Intrinsic::aarch64_sve_ptrue:
486     case Intrinsic::aarch64_sve_mul:
487     case Intrinsic::aarch64_sve_fmul:
488     case Intrinsic::aarch64_sve_tbl:
489       for (User *U : F.users())
490         Functions.insert(cast<Instruction>(U)->getFunction());
491       break;
492     default:
493       break;
494     }
495   }
496 
497   if (!Functions.empty())
498     Changed |= optimizeFunctions(Functions);
499 
500   return Changed;
501 }
502