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