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