1 //===- MVEGatherScatterLowering.cpp - Gather/Scatter lowering -------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 /// This pass custom lowers llvm.gather and llvm.scatter instructions to 10 /// arm.mve.gather and arm.mve.scatter intrinsics, optimising the code to 11 /// produce a better final result as we go. 12 // 13 //===----------------------------------------------------------------------===// 14 15 #include "ARM.h" 16 #include "ARMBaseInstrInfo.h" 17 #include "ARMSubtarget.h" 18 #include "llvm/Analysis/TargetTransformInfo.h" 19 #include "llvm/CodeGen/TargetLowering.h" 20 #include "llvm/CodeGen/TargetPassConfig.h" 21 #include "llvm/CodeGen/TargetSubtargetInfo.h" 22 #include "llvm/InitializePasses.h" 23 #include "llvm/IR/BasicBlock.h" 24 #include "llvm/IR/Constant.h" 25 #include "llvm/IR/Constants.h" 26 #include "llvm/IR/DerivedTypes.h" 27 #include "llvm/IR/Function.h" 28 #include "llvm/IR/InstrTypes.h" 29 #include "llvm/IR/Instruction.h" 30 #include "llvm/IR/Instructions.h" 31 #include "llvm/IR/IntrinsicInst.h" 32 #include "llvm/IR/Intrinsics.h" 33 #include "llvm/IR/IntrinsicsARM.h" 34 #include "llvm/IR/IRBuilder.h" 35 #include "llvm/IR/PatternMatch.h" 36 #include "llvm/IR/Type.h" 37 #include "llvm/IR/Value.h" 38 #include "llvm/Pass.h" 39 #include "llvm/Support/Casting.h" 40 #include <algorithm> 41 #include <cassert> 42 43 using namespace llvm; 44 45 #define DEBUG_TYPE "mve-gather-scatter-lowering" 46 47 cl::opt<bool> EnableMaskedGatherScatters( 48 "enable-arm-maskedgatscat", cl::Hidden, cl::init(false), 49 cl::desc("Enable the generation of masked gathers and scatters")); 50 51 namespace { 52 53 class MVEGatherScatterLowering : public FunctionPass { 54 public: 55 static char ID; // Pass identification, replacement for typeid 56 57 explicit MVEGatherScatterLowering() : FunctionPass(ID) { 58 initializeMVEGatherScatterLoweringPass(*PassRegistry::getPassRegistry()); 59 } 60 61 bool runOnFunction(Function &F) override; 62 63 StringRef getPassName() const override { 64 return "MVE gather/scatter lowering"; 65 } 66 67 void getAnalysisUsage(AnalysisUsage &AU) const override { 68 AU.setPreservesCFG(); 69 AU.addRequired<TargetPassConfig>(); 70 FunctionPass::getAnalysisUsage(AU); 71 } 72 73 private: 74 // Check this is a valid gather with correct alignment 75 bool isLegalTypeAndAlignment(unsigned NumElements, unsigned ElemSize, 76 unsigned Alignment); 77 // Check whether Ptr is hidden behind a bitcast and look through it 78 void lookThroughBitcast(Value *&Ptr); 79 // Check for a getelementptr and deduce base and offsets from it, on success 80 // returning the base directly and the offsets indirectly using the Offsets 81 // argument 82 Value *checkGEP(Value *&Offsets, Type *Ty, Value *Ptr, IRBuilder<> Builder); 83 // Compute the scale of this gather/scatter instruction 84 int computeScale(unsigned GEPElemSize, unsigned MemoryElemSize); 85 86 bool lowerGather(IntrinsicInst *I); 87 // Create a gather from a base + vector of offsets 88 Value *tryCreateMaskedGatherOffset(IntrinsicInst *I, Value *Ptr, 89 Instruction *&Root, IRBuilder<> Builder); 90 // Create a gather from a vector of pointers 91 Value *tryCreateMaskedGatherBase(IntrinsicInst *I, Value *Ptr, 92 IRBuilder<> Builder); 93 94 bool lowerScatter(IntrinsicInst *I); 95 // Create a scatter to a base + vector of offsets 96 Value *tryCreateMaskedScatterOffset(IntrinsicInst *I, Value *Ptr, 97 IRBuilder<> Builder); 98 // Create a scatter to a vector of pointers 99 Value *tryCreateMaskedScatterBase(IntrinsicInst *I, Value *Ptr, 100 IRBuilder<> Builder); 101 }; 102 103 } // end anonymous namespace 104 105 char MVEGatherScatterLowering::ID = 0; 106 107 INITIALIZE_PASS(MVEGatherScatterLowering, DEBUG_TYPE, 108 "MVE gather/scattering lowering pass", false, false) 109 110 Pass *llvm::createMVEGatherScatterLoweringPass() { 111 return new MVEGatherScatterLowering(); 112 } 113 114 bool MVEGatherScatterLowering::isLegalTypeAndAlignment(unsigned NumElements, 115 unsigned ElemSize, 116 unsigned Alignment) { 117 if (((NumElements == 4 && 118 (ElemSize == 32 || ElemSize == 16 || ElemSize == 8)) || 119 (NumElements == 8 && (ElemSize == 16 || ElemSize == 8)) || 120 (NumElements == 16 && ElemSize == 8)) && 121 ElemSize / 8 <= Alignment) 122 return true; 123 LLVM_DEBUG(dbgs() << "masked gathers/scatters: instruction does not have " 124 << "valid alignment or vector type \n"); 125 return false; 126 } 127 128 Value *MVEGatherScatterLowering::checkGEP(Value *&Offsets, Type *Ty, Value *Ptr, 129 IRBuilder<> Builder) { 130 GetElementPtrInst *GEP = dyn_cast<GetElementPtrInst>(Ptr); 131 if (!GEP) { 132 LLVM_DEBUG( 133 dbgs() << "masked gathers/scatters: no getelementpointer found\n"); 134 return nullptr; 135 } 136 LLVM_DEBUG(dbgs() << "masked gathers/scatters: getelementpointer found." 137 << " Looking at intrinsic for base + vector of offsets\n"); 138 Value *GEPPtr = GEP->getPointerOperand(); 139 if (GEPPtr->getType()->isVectorTy()) { 140 return nullptr; 141 } 142 if (GEP->getNumOperands() != 2) { 143 LLVM_DEBUG(dbgs() << "masked gathers/scatters: getelementptr with too many" 144 << " operands. Expanding.\n"); 145 return nullptr; 146 } 147 Offsets = GEP->getOperand(1); 148 // SExt offsets inside masked gathers are not permitted by the architecture; 149 // we therefore can't fold them 150 if (ZExtInst *ZextOffs = dyn_cast<ZExtInst>(Offsets)) 151 Offsets = ZextOffs->getOperand(0); 152 Type *OffsType = VectorType::getInteger(cast<VectorType>(Ty)); 153 // If the offset we found does not have the type the intrinsic expects, 154 // i.e., the same type as the gather (or scatter input) itself, we need to 155 // convert it (only i types) or fall back to expanding the gather 156 if (OffsType != Offsets->getType()) { 157 if (OffsType->getScalarSizeInBits() > 158 Offsets->getType()->getScalarSizeInBits()) { 159 LLVM_DEBUG(dbgs() << "masked gathers/scatters: extending offsets\n"); 160 Offsets = Builder.CreateZExt(Offsets, OffsType, ""); 161 } else { 162 LLVM_DEBUG(dbgs() << "masked gathers/scatters: no correct offset type." 163 << " Can't create intrinsic.\n"); 164 return nullptr; 165 } 166 } 167 // If none of the checks failed, return the gep's base pointer 168 return GEPPtr; 169 } 170 171 void MVEGatherScatterLowering::lookThroughBitcast(Value *&Ptr) { 172 // Look through bitcast instruction if #elements is the same 173 if (auto *BitCast = dyn_cast<BitCastInst>(Ptr)) { 174 Type *BCTy = BitCast->getType(); 175 Type *BCSrcTy = BitCast->getOperand(0)->getType(); 176 if (BCTy->getVectorNumElements() == BCSrcTy->getVectorNumElements()) { 177 LLVM_DEBUG( 178 dbgs() << "masked gathers/scatters: looking through bitcast\n"); 179 Ptr = BitCast->getOperand(0); 180 } 181 } 182 } 183 184 int MVEGatherScatterLowering::computeScale(unsigned GEPElemSize, 185 unsigned MemoryElemSize) { 186 // This can be a 32bit load/store scaled by 4, a 16bit load/store scaled by 2, 187 // or a 8bit, 16bit or 32bit load/store scaled by 1 188 if (GEPElemSize == 32 && MemoryElemSize == 32) 189 return 2; 190 else if (GEPElemSize == 16 && MemoryElemSize == 16) 191 return 1; 192 else if (GEPElemSize == 8) 193 return 0; 194 LLVM_DEBUG(dbgs() << "masked gathers/scatters: incorrect scale. Can't " 195 << "create intrinsic\n"); 196 return -1; 197 } 198 199 bool MVEGatherScatterLowering::lowerGather(IntrinsicInst *I) { 200 using namespace PatternMatch; 201 LLVM_DEBUG(dbgs() << "masked gathers: checking transform preconditions\n"); 202 203 // @llvm.masked.gather.*(Ptrs, alignment, Mask, Src0) 204 // Attempt to turn the masked gather in I into a MVE intrinsic 205 // Potentially optimising the addressing modes as we do so. 206 Type *Ty = I->getType(); 207 Value *Ptr = I->getArgOperand(0); 208 unsigned Alignment = cast<ConstantInt>(I->getArgOperand(1))->getZExtValue(); 209 Value *Mask = I->getArgOperand(2); 210 Value *PassThru = I->getArgOperand(3); 211 212 if (!isLegalTypeAndAlignment(Ty->getVectorNumElements(), 213 Ty->getScalarSizeInBits(), Alignment)) 214 return false; 215 lookThroughBitcast(Ptr); 216 assert(Ptr->getType()->isVectorTy() && "Unexpected pointer type"); 217 218 IRBuilder<> Builder(I->getContext()); 219 Builder.SetInsertPoint(I); 220 Builder.SetCurrentDebugLocation(I->getDebugLoc()); 221 222 Instruction *Root = I; 223 Value *Load = tryCreateMaskedGatherOffset(I, Ptr, Root, Builder); 224 if (!Load) 225 Load = tryCreateMaskedGatherBase(I, Ptr, Builder); 226 if (!Load) 227 return false; 228 229 if (!isa<UndefValue>(PassThru) && !match(PassThru, m_Zero())) { 230 LLVM_DEBUG(dbgs() << "masked gathers: found non-trivial passthru - " 231 << "creating select\n"); 232 Load = Builder.CreateSelect(Mask, Load, PassThru); 233 } 234 235 Root->replaceAllUsesWith(Load); 236 Root->eraseFromParent(); 237 if (Root != I) 238 // If this was an extending gather, we need to get rid of the sext/zext 239 // sext/zext as well as of the gather itself 240 I->eraseFromParent(); 241 LLVM_DEBUG(dbgs() << "masked gathers: successfully built masked gather\n"); 242 return true; 243 } 244 245 Value *MVEGatherScatterLowering::tryCreateMaskedGatherBase( 246 IntrinsicInst *I, Value *Ptr, IRBuilder<> Builder) { 247 using namespace PatternMatch; 248 Type *Ty = I->getType(); 249 LLVM_DEBUG(dbgs() << "masked gathers: loading from vector of pointers\n"); 250 if (Ty->getVectorNumElements() != 4 || Ty->getScalarSizeInBits() != 32) 251 // Can't build an intrinsic for this 252 return nullptr; 253 Value *Mask = I->getArgOperand(2); 254 if (match(Mask, m_One())) 255 return Builder.CreateIntrinsic(Intrinsic::arm_mve_vldr_gather_base, 256 {Ty, Ptr->getType()}, 257 {Ptr, Builder.getInt32(0)}); 258 else 259 return Builder.CreateIntrinsic( 260 Intrinsic::arm_mve_vldr_gather_base_predicated, 261 {Ty, Ptr->getType(), Mask->getType()}, 262 {Ptr, Builder.getInt32(0), Mask}); 263 } 264 265 Value *MVEGatherScatterLowering::tryCreateMaskedGatherOffset( 266 IntrinsicInst *I, Value *Ptr, Instruction *&Root, IRBuilder<> Builder) { 267 using namespace PatternMatch; 268 269 Type *OriginalTy = I->getType(); 270 Type *ResultTy = OriginalTy; 271 272 unsigned Unsigned = 1; 273 // The size of the gather was already checked in isLegalTypeAndAlignment; 274 // if it was not a full vector width an appropriate extend should follow. 275 auto *Extend = Root; 276 if (OriginalTy->getPrimitiveSizeInBits() < 128) { 277 // Only transform gathers with exactly one use 278 if (!I->hasOneUse()) 279 return nullptr; 280 281 // The correct root to replace is the not the CallInst itself, but the 282 // instruction which extends it 283 Extend = cast<Instruction>(*I->users().begin()); 284 if (isa<SExtInst>(Extend)) { 285 Unsigned = 0; 286 } else if (!isa<ZExtInst>(Extend)) { 287 LLVM_DEBUG(dbgs() << "masked gathers: extend needed but not provided. " 288 << "Expanding\n"); 289 return nullptr; 290 } 291 LLVM_DEBUG(dbgs() << "masked gathers: found an extending gather\n"); 292 ResultTy = Extend->getType(); 293 // The final size of the gather must be a full vector width 294 if (ResultTy->getPrimitiveSizeInBits() != 128) { 295 LLVM_DEBUG(dbgs() << "masked gathers: extending from the wrong type. " 296 << "Expanding\n"); 297 return nullptr; 298 } 299 } 300 301 Value *Offsets; 302 Value *BasePtr = checkGEP(Offsets, ResultTy, Ptr, Builder); 303 if (!BasePtr) 304 return nullptr; 305 306 int Scale = computeScale( 307 BasePtr->getType()->getPointerElementType()->getPrimitiveSizeInBits(), 308 OriginalTy->getScalarSizeInBits()); 309 if (Scale == -1) 310 return nullptr; 311 Root = Extend; 312 313 Value *Mask = I->getArgOperand(2); 314 if (!match(Mask, m_One())) 315 return Builder.CreateIntrinsic( 316 Intrinsic::arm_mve_vldr_gather_offset_predicated, 317 {ResultTy, BasePtr->getType(), Offsets->getType(), Mask->getType()}, 318 {BasePtr, Offsets, Builder.getInt32(OriginalTy->getScalarSizeInBits()), 319 Builder.getInt32(Scale), Builder.getInt32(Unsigned), Mask}); 320 else 321 return Builder.CreateIntrinsic( 322 Intrinsic::arm_mve_vldr_gather_offset, 323 {ResultTy, BasePtr->getType(), Offsets->getType()}, 324 {BasePtr, Offsets, Builder.getInt32(OriginalTy->getScalarSizeInBits()), 325 Builder.getInt32(Scale), Builder.getInt32(Unsigned)}); 326 } 327 328 bool MVEGatherScatterLowering::lowerScatter(IntrinsicInst *I) { 329 using namespace PatternMatch; 330 LLVM_DEBUG(dbgs() << "masked scatters: checking transform preconditions\n"); 331 332 // @llvm.masked.scatter.*(data, ptrs, alignment, mask) 333 // Attempt to turn the masked scatter in I into a MVE intrinsic 334 // Potentially optimising the addressing modes as we do so. 335 Value *Input = I->getArgOperand(0); 336 Value *Ptr = I->getArgOperand(1); 337 unsigned Alignment = cast<ConstantInt>(I->getArgOperand(2))->getZExtValue(); 338 Type *Ty = Input->getType(); 339 340 if (!isLegalTypeAndAlignment(Ty->getVectorNumElements(), 341 Ty->getScalarSizeInBits(), Alignment)) 342 return false; 343 lookThroughBitcast(Ptr); 344 assert(Ptr->getType()->isVectorTy() && "Unexpected pointer type"); 345 346 IRBuilder<> Builder(I->getContext()); 347 Builder.SetInsertPoint(I); 348 Builder.SetCurrentDebugLocation(I->getDebugLoc()); 349 350 Value *Store = tryCreateMaskedScatterOffset(I, Ptr, Builder); 351 if (!Store) 352 Store = tryCreateMaskedScatterBase(I, Ptr, Builder); 353 if (!Store) 354 return false; 355 356 LLVM_DEBUG(dbgs() << "masked scatters: successfully built masked scatter\n"); 357 I->replaceAllUsesWith(Store); 358 I->eraseFromParent(); 359 return true; 360 } 361 362 Value *MVEGatherScatterLowering::tryCreateMaskedScatterBase( 363 IntrinsicInst *I, Value *Ptr, IRBuilder<> Builder) { 364 using namespace PatternMatch; 365 Value *Input = I->getArgOperand(0); 366 Value *Mask = I->getArgOperand(3); 367 Type *Ty = Input->getType(); 368 // Only QR variants allow truncating 369 if (!(Ty->getVectorNumElements() == 4 && Ty->getScalarSizeInBits() == 32)) { 370 // Can't build an intrinsic for this 371 return nullptr; 372 } 373 // int_arm_mve_vstr_scatter_base(_predicated) addr, offset, data(, mask) 374 LLVM_DEBUG(dbgs() << "masked scatters: storing to a vector of pointers\n"); 375 if (match(Mask, m_One())) 376 return Builder.CreateIntrinsic(Intrinsic::arm_mve_vstr_scatter_base, 377 {Ptr->getType(), Input->getType()}, 378 {Ptr, Builder.getInt32(0), Input}); 379 else 380 return Builder.CreateIntrinsic( 381 Intrinsic::arm_mve_vstr_scatter_base_predicated, 382 {Ptr->getType(), Input->getType(), Mask->getType()}, 383 {Ptr, Builder.getInt32(0), Input, Mask}); 384 } 385 386 Value *MVEGatherScatterLowering::tryCreateMaskedScatterOffset( 387 IntrinsicInst *I, Value *Ptr, IRBuilder<> Builder) { 388 using namespace PatternMatch; 389 Value *Input = I->getArgOperand(0); 390 Value *Mask = I->getArgOperand(3); 391 Type *InputTy = Input->getType(); 392 Type *MemoryTy = InputTy; 393 LLVM_DEBUG(dbgs() << "masked scatters: getelementpointer found. Storing" 394 << " to base + vector of offsets\n"); 395 // If the input has been truncated, try to integrate that trunc into the 396 // scatter instruction (we don't care about alignment here) 397 if (TruncInst *Trunc = dyn_cast<TruncInst>(Input)) { 398 Value *PreTrunc = Trunc->getOperand(0); 399 Type *PreTruncTy = PreTrunc->getType(); 400 if (PreTruncTy->getPrimitiveSizeInBits() == 128) { 401 Input = PreTrunc; 402 InputTy = PreTruncTy; 403 } 404 } 405 if (InputTy->getPrimitiveSizeInBits() != 128) { 406 LLVM_DEBUG( 407 dbgs() << "masked scatters: cannot create scatters for non-standard" 408 << " input types. Expanding.\n"); 409 return nullptr; 410 } 411 412 Value *Offsets; 413 Value *BasePtr = checkGEP(Offsets, InputTy, Ptr, Builder); 414 if (!BasePtr) 415 return nullptr; 416 int Scale = computeScale( 417 BasePtr->getType()->getPointerElementType()->getPrimitiveSizeInBits(), 418 MemoryTy->getScalarSizeInBits()); 419 if (Scale == -1) 420 return nullptr; 421 422 if (!match(Mask, m_One())) 423 return Builder.CreateIntrinsic( 424 Intrinsic::arm_mve_vstr_scatter_offset_predicated, 425 {BasePtr->getType(), Offsets->getType(), Input->getType(), 426 Mask->getType()}, 427 {BasePtr, Offsets, Input, 428 Builder.getInt32(MemoryTy->getScalarSizeInBits()), 429 Builder.getInt32(Scale), Mask}); 430 else 431 return Builder.CreateIntrinsic( 432 Intrinsic::arm_mve_vstr_scatter_offset, 433 {BasePtr->getType(), Offsets->getType(), Input->getType()}, 434 {BasePtr, Offsets, Input, 435 Builder.getInt32(MemoryTy->getScalarSizeInBits()), 436 Builder.getInt32(Scale)}); 437 } 438 439 bool MVEGatherScatterLowering::runOnFunction(Function &F) { 440 if (!EnableMaskedGatherScatters) 441 return false; 442 auto &TPC = getAnalysis<TargetPassConfig>(); 443 auto &TM = TPC.getTM<TargetMachine>(); 444 auto *ST = &TM.getSubtarget<ARMSubtarget>(F); 445 if (!ST->hasMVEIntegerOps()) 446 return false; 447 SmallVector<IntrinsicInst *, 4> Gathers; 448 SmallVector<IntrinsicInst *, 4> Scatters; 449 for (BasicBlock &BB : F) { 450 for (Instruction &I : BB) { 451 IntrinsicInst *II = dyn_cast<IntrinsicInst>(&I); 452 if (II && II->getIntrinsicID() == Intrinsic::masked_gather) 453 Gathers.push_back(II); 454 else if (II && II->getIntrinsicID() == Intrinsic::masked_scatter) 455 Scatters.push_back(II); 456 } 457 } 458 459 bool Changed = false; 460 for (IntrinsicInst *I : Gathers) 461 Changed |= lowerGather(I); 462 for (IntrinsicInst *I : Scatters) 463 Changed |= lowerScatter(I); 464 465 return Changed; 466 } 467