1 //===- ReduceOpcodes.cpp - Specialized Delta Pass -------------------------===// 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 // Try to replace instructions that are likely to codegen to simpler or smaller 10 // sequences. This is a fuzzy and target specific concept. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "ReduceOpcodes.h" 15 #include "Delta.h" 16 #include "llvm/IR/IRBuilder.h" 17 #include "llvm/IR/Instructions.h" 18 #include "llvm/IR/IntrinsicInst.h" 19 #include "llvm/IR/Intrinsics.h" 20 #include "llvm/IR/IntrinsicsAMDGPU.h" 21 22 // Assume outgoing undef arguments aren't relevant. 23 // TODO: Maybe skip any trivial constant arguments. 24 static bool shouldIgnoreArgument(const Value *V) { 25 return isa<UndefValue>(V); 26 } 27 28 static Value *replaceIntrinsic(Module &M, IntrinsicInst *II, 29 Intrinsic::ID NewIID, 30 ArrayRef<Type *> Tys = std::nullopt) { 31 Function *NewFunc = Intrinsic::getDeclaration(&M, NewIID, Tys); 32 II->setCalledFunction(NewFunc); 33 return II; 34 } 35 36 static Value *reduceIntrinsic(Oracle &O, Module &M, IntrinsicInst *II) { 37 IRBuilder<> B(II); 38 switch (II->getIntrinsicID()) { 39 case Intrinsic::sqrt: 40 if (O.shouldKeep()) 41 return nullptr; 42 43 return B.CreateFMul(II->getArgOperand(0), 44 ConstantFP::get(II->getType(), 2.0)); 45 case Intrinsic::minnum: 46 case Intrinsic::maxnum: 47 case Intrinsic::minimum: 48 case Intrinsic::maximum: 49 case Intrinsic::amdgcn_fmul_legacy: 50 if (O.shouldKeep()) 51 return nullptr; 52 return B.CreateFMul(II->getArgOperand(0), II->getArgOperand(1)); 53 case Intrinsic::amdgcn_workitem_id_y: 54 case Intrinsic::amdgcn_workitem_id_z: 55 if (O.shouldKeep()) 56 return nullptr; 57 return replaceIntrinsic(M, II, Intrinsic::amdgcn_workitem_id_x); 58 case Intrinsic::amdgcn_workgroup_id_y: 59 case Intrinsic::amdgcn_workgroup_id_z: 60 if (O.shouldKeep()) 61 return nullptr; 62 return replaceIntrinsic(M, II, Intrinsic::amdgcn_workgroup_id_x); 63 case Intrinsic::amdgcn_div_fixup: 64 case Intrinsic::amdgcn_fma_legacy: 65 if (O.shouldKeep()) 66 return nullptr; 67 return replaceIntrinsic(M, II, Intrinsic::fma, {II->getType()}); 68 default: 69 return nullptr; 70 } 71 } 72 73 /// Look for calls that look like they could be replaced with a load or store. 74 static bool callLooksLikeLoadStore(CallBase *CB, Value *&DataArg, 75 Value *&PtrArg) { 76 const bool IsStore = CB->getType()->isVoidTy(); 77 78 PtrArg = nullptr; 79 DataArg = nullptr; 80 for (Value *Arg : CB->args()) { 81 if (shouldIgnoreArgument(Arg)) 82 continue; 83 84 if (!Arg->getType()->isSized()) 85 return false; 86 87 PointerType *PT = dyn_cast<PointerType>(Arg->getType()); 88 if (!PtrArg && PT) { 89 // FIXME: Could create bitcast for typed pointers, but roll back unused 90 // replacement only erases one instruction. 91 if (!IsStore && !PT->isOpaqueOrPointeeTypeMatches(CB->getType())) 92 return false; 93 94 PtrArg = Arg; 95 continue; 96 } 97 98 if (!IsStore || DataArg) 99 return false; 100 101 DataArg = Arg; 102 } 103 104 if (IsStore && !DataArg) { 105 // FIXME: For typed pointers, use element type? 106 DataArg = ConstantInt::get(IntegerType::getInt32Ty(CB->getContext()), 0); 107 } 108 109 // If we didn't find any arguments, we can fill in the pointer. 110 if (!PtrArg) { 111 unsigned AS = CB->getModule()->getDataLayout().getAllocaAddrSpace(); 112 113 PointerType *PtrTy = 114 PointerType::get(DataArg ? DataArg->getType() 115 : IntegerType::getInt32Ty(CB->getContext()), 116 AS); 117 118 PtrArg = ConstantPointerNull::get(PtrTy); 119 } 120 121 // Make sure we don't emit an invalid store with typed pointers. 122 if (IsStore && DataArg->getType()->getPointerTo( 123 cast<PointerType>(PtrArg->getType())->getAddressSpace()) != 124 PtrArg->getType()) 125 return false; 126 127 return true; 128 } 129 130 // TODO: Replace 2 pointer argument calls with memcpy 131 static Value *tryReplaceCallWithLoadStore(Oracle &O, Module &M, CallBase *CB) { 132 Value *PtrArg = nullptr; 133 Value *DataArg = nullptr; 134 if (!callLooksLikeLoadStore(CB, DataArg, PtrArg) || O.shouldKeep()) 135 return nullptr; 136 137 IRBuilder<> B(CB); 138 if (DataArg) 139 return B.CreateStore(DataArg, PtrArg, true); 140 return B.CreateLoad(CB->getType(), PtrArg, true); 141 } 142 143 static bool callLooksLikeOperator(CallBase *CB, 144 SmallVectorImpl<Value *> &OperatorArgs) { 145 Type *ReturnTy = CB->getType(); 146 if (!ReturnTy->isFirstClassType()) 147 return false; 148 149 for (Value *Arg : CB->args()) { 150 if (shouldIgnoreArgument(Arg)) 151 continue; 152 153 if (Arg->getType() != ReturnTy) 154 return false; 155 156 OperatorArgs.push_back(Arg); 157 } 158 159 return true; 160 } 161 162 static Value *tryReplaceCallWithOperator(Oracle &O, Module &M, CallBase *CB) { 163 SmallVector<Value *, 4> Arguments; 164 165 if (!callLooksLikeOperator(CB, Arguments) || Arguments.size() > 3) 166 return nullptr; 167 168 if (O.shouldKeep()) 169 return nullptr; 170 171 IRBuilder<> B(CB); 172 if (CB->getType()->isFPOrFPVectorTy()) { 173 switch (Arguments.size()) { 174 case 1: 175 return B.CreateFNeg(Arguments[0]); 176 case 2: 177 return B.CreateFMul(Arguments[0], Arguments[1]); 178 case 3: 179 return B.CreateIntrinsic(Intrinsic::fma, {CB->getType()}, Arguments); 180 default: 181 return nullptr; 182 } 183 184 llvm_unreachable("all argument sizes handled"); 185 } 186 187 if (CB->getType()->isIntOrIntVectorTy()) { 188 switch (Arguments.size()) { 189 case 1: 190 return B.CreateUnaryIntrinsic(Intrinsic::bswap, Arguments[0]); 191 case 2: 192 return B.CreateAnd(Arguments[0], Arguments[1]); 193 case 3: 194 return B.CreateIntrinsic(Intrinsic::fshl, {CB->getType()}, Arguments); 195 default: 196 return nullptr; 197 } 198 199 llvm_unreachable("all argument sizes handled"); 200 } 201 202 return nullptr; 203 } 204 205 static Value *reduceInstruction(Oracle &O, Module &M, Instruction &I) { 206 IRBuilder<> B(&I); 207 208 // TODO: fp binary operator with constant to fneg 209 switch (I.getOpcode()) { 210 case Instruction::FDiv: 211 case Instruction::FRem: 212 if (O.shouldKeep()) 213 return nullptr; 214 215 // Divisions tends to codegen into a long sequence or a library call. 216 return B.CreateFMul(I.getOperand(0), I.getOperand(1)); 217 case Instruction::UDiv: 218 case Instruction::SDiv: 219 case Instruction::URem: 220 case Instruction::SRem: 221 if (O.shouldKeep()) 222 return nullptr; 223 224 // Divisions tends to codegen into a long sequence or a library call. 225 return B.CreateMul(I.getOperand(0), I.getOperand(1)); 226 case Instruction::Add: 227 case Instruction::Sub: { 228 if (O.shouldKeep()) 229 return nullptr; 230 231 // Add/sub are more likely codegen to instructions with carry out side 232 // effects. 233 return B.CreateOr(I.getOperand(0), I.getOperand(1)); 234 } 235 case Instruction::Call: { 236 if (IntrinsicInst *II = dyn_cast<IntrinsicInst>(&I)) 237 return reduceIntrinsic(O, M, II); 238 239 CallBase *CB = cast<CallBase>(&I); 240 241 if (Value *NewOp = tryReplaceCallWithOperator(O, M, CB)) 242 return NewOp; 243 244 if (Value *NewOp = tryReplaceCallWithLoadStore(O, M, CB)) 245 return NewOp; 246 247 return nullptr; 248 } 249 default: 250 return nullptr; 251 } 252 253 return nullptr; 254 } 255 256 static void replaceOpcodesInModule(Oracle &O, ReducerWorkItem &WorkItem) { 257 Module &Mod = WorkItem.getModule(); 258 259 for (Function &F : Mod) { 260 for (BasicBlock &BB : F) 261 for (Instruction &I : make_early_inc_range(BB)) { 262 Instruction *Replacement = 263 dyn_cast_or_null<Instruction>(reduceInstruction(O, Mod, I)); 264 if (Replacement && Replacement != &I) { 265 if (isa<FPMathOperator>(Replacement)) 266 Replacement->copyFastMathFlags(&I); 267 268 Replacement->copyIRFlags(&I); 269 Replacement->copyMetadata(I); 270 Replacement->takeName(&I); 271 I.replaceAllUsesWith(Replacement); 272 I.eraseFromParent(); 273 } 274 } 275 } 276 } 277 278 void llvm::reduceOpcodesDeltaPass(TestRunner &Test) { 279 runDeltaPass(Test, replaceOpcodesInModule, "Reducing Opcodes"); 280 } 281