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