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 = {}) { 33 Function *NewFunc = Intrinsic::getOrInsertDeclaration(&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->getDataLayout().getAllocaAddrSpace(); 108 109 PointerType *PtrTy = PointerType::get(CB->getContext(), AS); 110 111 PtrArg = ConstantPointerNull::get(PtrTy); 112 } 113 114 return true; 115 } 116 117 // TODO: Replace 2 pointer argument calls with memcpy 118 static Value *tryReplaceCallWithLoadStore(Oracle &O, Module &M, CallBase *CB) { 119 Value *PtrArg = nullptr; 120 Value *DataArg = nullptr; 121 if (!callLooksLikeLoadStore(CB, DataArg, PtrArg) || O.shouldKeep()) 122 return nullptr; 123 124 IRBuilder<> B(CB); 125 if (DataArg) 126 return B.CreateStore(DataArg, PtrArg, true); 127 return B.CreateLoad(CB->getType(), PtrArg, true); 128 } 129 130 static bool callLooksLikeOperator(CallBase *CB, 131 SmallVectorImpl<Value *> &OperatorArgs) { 132 Type *ReturnTy = CB->getType(); 133 if (!ReturnTy->isFirstClassType()) 134 return false; 135 136 for (Value *Arg : CB->args()) { 137 if (shouldIgnoreArgument(Arg)) 138 continue; 139 140 if (Arg->getType() != ReturnTy) 141 return false; 142 143 OperatorArgs.push_back(Arg); 144 } 145 146 return true; 147 } 148 149 static Value *tryReplaceCallWithOperator(Oracle &O, Module &M, CallBase *CB) { 150 SmallVector<Value *, 4> Arguments; 151 152 if (!callLooksLikeOperator(CB, Arguments) || Arguments.size() > 3) 153 return nullptr; 154 155 if (O.shouldKeep()) 156 return nullptr; 157 158 IRBuilder<> B(CB); 159 if (CB->getType()->isFPOrFPVectorTy()) { 160 switch (Arguments.size()) { 161 case 1: 162 return B.CreateFNeg(Arguments[0]); 163 case 2: 164 return B.CreateFMul(Arguments[0], Arguments[1]); 165 case 3: 166 return B.CreateIntrinsic(Intrinsic::fma, {CB->getType()}, Arguments); 167 default: 168 return nullptr; 169 } 170 171 llvm_unreachable("all argument sizes handled"); 172 } 173 174 if (CB->getType()->isIntOrIntVectorTy()) { 175 switch (Arguments.size()) { 176 case 1: 177 return B.CreateUnaryIntrinsic(Intrinsic::bswap, Arguments[0]); 178 case 2: 179 return B.CreateAnd(Arguments[0], Arguments[1]); 180 case 3: 181 return B.CreateIntrinsic(Intrinsic::fshl, {CB->getType()}, Arguments); 182 default: 183 return nullptr; 184 } 185 186 llvm_unreachable("all argument sizes handled"); 187 } 188 189 return nullptr; 190 } 191 192 static Value *reduceInstruction(Oracle &O, Module &M, Instruction &I) { 193 IRBuilder<> B(&I); 194 195 // TODO: fp binary operator with constant to fneg 196 switch (I.getOpcode()) { 197 case Instruction::FDiv: 198 case Instruction::FRem: 199 if (O.shouldKeep()) 200 return nullptr; 201 202 // Divisions tends to codegen into a long sequence or a library call. 203 return B.CreateFMul(I.getOperand(0), I.getOperand(1)); 204 case Instruction::UDiv: 205 case Instruction::SDiv: 206 case Instruction::URem: 207 case Instruction::SRem: 208 if (O.shouldKeep()) 209 return nullptr; 210 211 // Divisions tends to codegen into a long sequence or a library call. 212 return B.CreateMul(I.getOperand(0), I.getOperand(1)); 213 case Instruction::Add: 214 case Instruction::Sub: { 215 if (O.shouldKeep()) 216 return nullptr; 217 218 // Add/sub are more likely codegen to instructions with carry out side 219 // effects. 220 return B.CreateOr(I.getOperand(0), I.getOperand(1)); 221 } 222 case Instruction::Call: { 223 if (IntrinsicInst *II = dyn_cast<IntrinsicInst>(&I)) 224 return reduceIntrinsic(O, M, II); 225 226 CallBase *CB = cast<CallBase>(&I); 227 228 if (Value *NewOp = tryReplaceCallWithOperator(O, M, CB)) 229 return NewOp; 230 231 if (Value *NewOp = tryReplaceCallWithLoadStore(O, M, CB)) 232 return NewOp; 233 234 return nullptr; 235 } 236 default: 237 return nullptr; 238 } 239 240 return nullptr; 241 } 242 243 static void replaceOpcodesInModule(Oracle &O, ReducerWorkItem &WorkItem) { 244 Module &Mod = WorkItem.getModule(); 245 246 for (Function &F : Mod) { 247 for (BasicBlock &BB : F) 248 for (Instruction &I : make_early_inc_range(BB)) { 249 Instruction *Replacement = 250 dyn_cast_or_null<Instruction>(reduceInstruction(O, Mod, I)); 251 if (Replacement && Replacement != &I) { 252 if (isa<FPMathOperator>(Replacement)) 253 Replacement->copyFastMathFlags(&I); 254 255 Replacement->copyIRFlags(&I); 256 Replacement->copyMetadata(I); 257 Replacement->takeName(&I); 258 I.replaceAllUsesWith(Replacement); 259 I.eraseFromParent(); 260 } 261 } 262 } 263 } 264 265 void llvm::reduceOpcodesDeltaPass(TestRunner &Test) { 266 runDeltaPass(Test, replaceOpcodesInModule, "Reducing Opcodes"); 267 } 268