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