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