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