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