xref: /llvm-project/llvm/tools/llvm-reduce/deltas/ReduceOpcodes.cpp (revision 416f1c465db62d829283f6902ef35e027e127aa7)
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