1573a5de7SMatt Arsenault //===- ReduceOpcodes.cpp - Specialized Delta Pass -------------------------===// 2573a5de7SMatt Arsenault // 3573a5de7SMatt Arsenault // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4573a5de7SMatt Arsenault // See https://llvm.org/LICENSE.txt for license information. 5573a5de7SMatt Arsenault // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6573a5de7SMatt Arsenault // 7573a5de7SMatt Arsenault //===----------------------------------------------------------------------===// 8573a5de7SMatt Arsenault // 9573a5de7SMatt Arsenault // Try to replace instructions that are likely to codegen to simpler or smaller 10573a5de7SMatt Arsenault // sequences. This is a fuzzy and target specific concept. 11573a5de7SMatt Arsenault // 12573a5de7SMatt Arsenault //===----------------------------------------------------------------------===// 13573a5de7SMatt Arsenault 14573a5de7SMatt Arsenault #include "ReduceOpcodes.h" 15573a5de7SMatt Arsenault #include "Delta.h" 16573a5de7SMatt Arsenault #include "llvm/IR/IRBuilder.h" 17573a5de7SMatt Arsenault #include "llvm/IR/Instructions.h" 18573a5de7SMatt Arsenault #include "llvm/IR/IntrinsicInst.h" 19573a5de7SMatt Arsenault #include "llvm/IR/Intrinsics.h" 20573a5de7SMatt Arsenault #include "llvm/IR/IntrinsicsAMDGPU.h" 21573a5de7SMatt Arsenault 22333ffafbSMatt Arsenault using namespace llvm; 23333ffafbSMatt Arsenault 244638ba7bSMatt Arsenault // Assume outgoing undef arguments aren't relevant. 254638ba7bSMatt Arsenault // TODO: Maybe skip any trivial constant arguments. 264638ba7bSMatt Arsenault static bool shouldIgnoreArgument(const Value *V) { 274638ba7bSMatt Arsenault return isa<UndefValue>(V); 284638ba7bSMatt Arsenault } 294638ba7bSMatt Arsenault 30573a5de7SMatt Arsenault static Value *replaceIntrinsic(Module &M, IntrinsicInst *II, 31573a5de7SMatt Arsenault Intrinsic::ID NewIID, 32e03f4271SJay Foad ArrayRef<Type *> Tys = {}) { 33fa789dffSRahul Joshi Function *NewFunc = Intrinsic::getOrInsertDeclaration(&M, NewIID, Tys); 34573a5de7SMatt Arsenault II->setCalledFunction(NewFunc); 35573a5de7SMatt Arsenault return II; 36573a5de7SMatt Arsenault } 37573a5de7SMatt Arsenault 383e6f7ab8SMatt Arsenault static Value *reduceIntrinsic(Oracle &O, Module &M, IntrinsicInst *II) { 393e6f7ab8SMatt Arsenault IRBuilder<> B(II); 403e6f7ab8SMatt Arsenault switch (II->getIntrinsicID()) { 413e6f7ab8SMatt Arsenault case Intrinsic::sqrt: 423e6f7ab8SMatt Arsenault if (O.shouldKeep()) 433e6f7ab8SMatt Arsenault return nullptr; 443e6f7ab8SMatt Arsenault 453e6f7ab8SMatt Arsenault return B.CreateFMul(II->getArgOperand(0), 463e6f7ab8SMatt Arsenault ConstantFP::get(II->getType(), 2.0)); 473e6f7ab8SMatt Arsenault case Intrinsic::minnum: 483e6f7ab8SMatt Arsenault case Intrinsic::maxnum: 493e6f7ab8SMatt Arsenault case Intrinsic::minimum: 503e6f7ab8SMatt Arsenault case Intrinsic::maximum: 513e6f7ab8SMatt Arsenault case Intrinsic::amdgcn_fmul_legacy: 523e6f7ab8SMatt Arsenault if (O.shouldKeep()) 533e6f7ab8SMatt Arsenault return nullptr; 543e6f7ab8SMatt Arsenault return B.CreateFMul(II->getArgOperand(0), II->getArgOperand(1)); 553e6f7ab8SMatt Arsenault case Intrinsic::amdgcn_workitem_id_y: 563e6f7ab8SMatt Arsenault case Intrinsic::amdgcn_workitem_id_z: 573e6f7ab8SMatt Arsenault if (O.shouldKeep()) 583e6f7ab8SMatt Arsenault return nullptr; 593e6f7ab8SMatt Arsenault return replaceIntrinsic(M, II, Intrinsic::amdgcn_workitem_id_x); 603e6f7ab8SMatt Arsenault case Intrinsic::amdgcn_workgroup_id_y: 613e6f7ab8SMatt Arsenault case Intrinsic::amdgcn_workgroup_id_z: 623e6f7ab8SMatt Arsenault if (O.shouldKeep()) 633e6f7ab8SMatt Arsenault return nullptr; 643e6f7ab8SMatt Arsenault return replaceIntrinsic(M, II, Intrinsic::amdgcn_workgroup_id_x); 653e6f7ab8SMatt Arsenault case Intrinsic::amdgcn_div_fixup: 663e6f7ab8SMatt Arsenault case Intrinsic::amdgcn_fma_legacy: 673e6f7ab8SMatt Arsenault if (O.shouldKeep()) 683e6f7ab8SMatt Arsenault return nullptr; 693e6f7ab8SMatt Arsenault return replaceIntrinsic(M, II, Intrinsic::fma, {II->getType()}); 703e6f7ab8SMatt Arsenault default: 713e6f7ab8SMatt Arsenault return nullptr; 723e6f7ab8SMatt Arsenault } 733e6f7ab8SMatt Arsenault } 743e6f7ab8SMatt Arsenault 754638ba7bSMatt Arsenault /// Look for calls that look like they could be replaced with a load or store. 764638ba7bSMatt Arsenault static bool callLooksLikeLoadStore(CallBase *CB, Value *&DataArg, 774638ba7bSMatt Arsenault Value *&PtrArg) { 784638ba7bSMatt Arsenault const bool IsStore = CB->getType()->isVoidTy(); 794638ba7bSMatt Arsenault 804638ba7bSMatt Arsenault PtrArg = nullptr; 814638ba7bSMatt Arsenault DataArg = nullptr; 824638ba7bSMatt Arsenault for (Value *Arg : CB->args()) { 834638ba7bSMatt Arsenault if (shouldIgnoreArgument(Arg)) 844638ba7bSMatt Arsenault continue; 854638ba7bSMatt Arsenault 864638ba7bSMatt Arsenault if (!Arg->getType()->isSized()) 874638ba7bSMatt Arsenault return false; 884638ba7bSMatt Arsenault 8935bdcb03SNikita Popov if (!PtrArg && Arg->getType()->isPointerTy()) { 904638ba7bSMatt Arsenault PtrArg = Arg; 914638ba7bSMatt Arsenault continue; 924638ba7bSMatt Arsenault } 934638ba7bSMatt Arsenault 944638ba7bSMatt Arsenault if (!IsStore || DataArg) 954638ba7bSMatt Arsenault return false; 964638ba7bSMatt Arsenault 974638ba7bSMatt Arsenault DataArg = Arg; 984638ba7bSMatt Arsenault } 994638ba7bSMatt Arsenault 1004638ba7bSMatt Arsenault if (IsStore && !DataArg) { 1014638ba7bSMatt Arsenault // FIXME: For typed pointers, use element type? 1024638ba7bSMatt Arsenault DataArg = ConstantInt::get(IntegerType::getInt32Ty(CB->getContext()), 0); 1034638ba7bSMatt Arsenault } 1044638ba7bSMatt Arsenault 1054638ba7bSMatt Arsenault // If we didn't find any arguments, we can fill in the pointer. 1064638ba7bSMatt Arsenault if (!PtrArg) { 1072d209d96SNikita Popov unsigned AS = CB->getDataLayout().getAllocaAddrSpace(); 1084638ba7bSMatt Arsenault 109*416f1c46SMats Jun Larsen PointerType *PtrTy = PointerType::get(CB->getContext(), AS); 1104638ba7bSMatt Arsenault 1114638ba7bSMatt Arsenault PtrArg = ConstantPointerNull::get(PtrTy); 1124638ba7bSMatt Arsenault } 1134638ba7bSMatt Arsenault 1144638ba7bSMatt Arsenault return true; 1154638ba7bSMatt Arsenault } 1164638ba7bSMatt Arsenault 1174638ba7bSMatt Arsenault // TODO: Replace 2 pointer argument calls with memcpy 1184638ba7bSMatt Arsenault static Value *tryReplaceCallWithLoadStore(Oracle &O, Module &M, CallBase *CB) { 1194638ba7bSMatt Arsenault Value *PtrArg = nullptr; 1204638ba7bSMatt Arsenault Value *DataArg = nullptr; 1214638ba7bSMatt Arsenault if (!callLooksLikeLoadStore(CB, DataArg, PtrArg) || O.shouldKeep()) 1224638ba7bSMatt Arsenault return nullptr; 1234638ba7bSMatt Arsenault 1244638ba7bSMatt Arsenault IRBuilder<> B(CB); 1254638ba7bSMatt Arsenault if (DataArg) 1264638ba7bSMatt Arsenault return B.CreateStore(DataArg, PtrArg, true); 1274638ba7bSMatt Arsenault return B.CreateLoad(CB->getType(), PtrArg, true); 1284638ba7bSMatt Arsenault } 1294638ba7bSMatt Arsenault 1304638ba7bSMatt Arsenault static bool callLooksLikeOperator(CallBase *CB, 1314638ba7bSMatt Arsenault SmallVectorImpl<Value *> &OperatorArgs) { 1324638ba7bSMatt Arsenault Type *ReturnTy = CB->getType(); 1334638ba7bSMatt Arsenault if (!ReturnTy->isFirstClassType()) 1344638ba7bSMatt Arsenault return false; 1354638ba7bSMatt Arsenault 1364638ba7bSMatt Arsenault for (Value *Arg : CB->args()) { 1374638ba7bSMatt Arsenault if (shouldIgnoreArgument(Arg)) 1384638ba7bSMatt Arsenault continue; 1394638ba7bSMatt Arsenault 1404638ba7bSMatt Arsenault if (Arg->getType() != ReturnTy) 1414638ba7bSMatt Arsenault return false; 1424638ba7bSMatt Arsenault 1434638ba7bSMatt Arsenault OperatorArgs.push_back(Arg); 1444638ba7bSMatt Arsenault } 1454638ba7bSMatt Arsenault 1464638ba7bSMatt Arsenault return true; 1474638ba7bSMatt Arsenault } 1484638ba7bSMatt Arsenault 1494638ba7bSMatt Arsenault static Value *tryReplaceCallWithOperator(Oracle &O, Module &M, CallBase *CB) { 1504638ba7bSMatt Arsenault SmallVector<Value *, 4> Arguments; 1514638ba7bSMatt Arsenault 1524638ba7bSMatt Arsenault if (!callLooksLikeOperator(CB, Arguments) || Arguments.size() > 3) 1534638ba7bSMatt Arsenault return nullptr; 1544638ba7bSMatt Arsenault 1554638ba7bSMatt Arsenault if (O.shouldKeep()) 1564638ba7bSMatt Arsenault return nullptr; 1574638ba7bSMatt Arsenault 1584638ba7bSMatt Arsenault IRBuilder<> B(CB); 1594638ba7bSMatt Arsenault if (CB->getType()->isFPOrFPVectorTy()) { 1604638ba7bSMatt Arsenault switch (Arguments.size()) { 1614638ba7bSMatt Arsenault case 1: 1624638ba7bSMatt Arsenault return B.CreateFNeg(Arguments[0]); 1634638ba7bSMatt Arsenault case 2: 1644638ba7bSMatt Arsenault return B.CreateFMul(Arguments[0], Arguments[1]); 1654638ba7bSMatt Arsenault case 3: 1664638ba7bSMatt Arsenault return B.CreateIntrinsic(Intrinsic::fma, {CB->getType()}, Arguments); 1674638ba7bSMatt Arsenault default: 1684638ba7bSMatt Arsenault return nullptr; 1694638ba7bSMatt Arsenault } 1704638ba7bSMatt Arsenault 1714638ba7bSMatt Arsenault llvm_unreachable("all argument sizes handled"); 1724638ba7bSMatt Arsenault } 1734638ba7bSMatt Arsenault 1744638ba7bSMatt Arsenault if (CB->getType()->isIntOrIntVectorTy()) { 1754638ba7bSMatt Arsenault switch (Arguments.size()) { 1764638ba7bSMatt Arsenault case 1: 1774638ba7bSMatt Arsenault return B.CreateUnaryIntrinsic(Intrinsic::bswap, Arguments[0]); 1784638ba7bSMatt Arsenault case 2: 1794638ba7bSMatt Arsenault return B.CreateAnd(Arguments[0], Arguments[1]); 1804638ba7bSMatt Arsenault case 3: 1814638ba7bSMatt Arsenault return B.CreateIntrinsic(Intrinsic::fshl, {CB->getType()}, Arguments); 1824638ba7bSMatt Arsenault default: 1834638ba7bSMatt Arsenault return nullptr; 1844638ba7bSMatt Arsenault } 1854638ba7bSMatt Arsenault 1864638ba7bSMatt Arsenault llvm_unreachable("all argument sizes handled"); 1874638ba7bSMatt Arsenault } 1884638ba7bSMatt Arsenault 1894638ba7bSMatt Arsenault return nullptr; 1904638ba7bSMatt Arsenault } 1914638ba7bSMatt Arsenault 1923e6f7ab8SMatt Arsenault static Value *reduceInstruction(Oracle &O, Module &M, Instruction &I) { 193573a5de7SMatt Arsenault IRBuilder<> B(&I); 1944638ba7bSMatt Arsenault 1954638ba7bSMatt Arsenault // TODO: fp binary operator with constant to fneg 196573a5de7SMatt Arsenault switch (I.getOpcode()) { 197573a5de7SMatt Arsenault case Instruction::FDiv: 198573a5de7SMatt Arsenault case Instruction::FRem: 1993e6f7ab8SMatt Arsenault if (O.shouldKeep()) 2003e6f7ab8SMatt Arsenault return nullptr; 2013e6f7ab8SMatt Arsenault 202573a5de7SMatt Arsenault // Divisions tends to codegen into a long sequence or a library call. 203573a5de7SMatt Arsenault return B.CreateFMul(I.getOperand(0), I.getOperand(1)); 204573a5de7SMatt Arsenault case Instruction::UDiv: 205573a5de7SMatt Arsenault case Instruction::SDiv: 206573a5de7SMatt Arsenault case Instruction::URem: 207573a5de7SMatt Arsenault case Instruction::SRem: 2083e6f7ab8SMatt Arsenault if (O.shouldKeep()) 2093e6f7ab8SMatt Arsenault return nullptr; 2103e6f7ab8SMatt Arsenault 211573a5de7SMatt Arsenault // Divisions tends to codegen into a long sequence or a library call. 212573a5de7SMatt Arsenault return B.CreateMul(I.getOperand(0), I.getOperand(1)); 213573a5de7SMatt Arsenault case Instruction::Add: 214573a5de7SMatt Arsenault case Instruction::Sub: { 2153e6f7ab8SMatt Arsenault if (O.shouldKeep()) 2163e6f7ab8SMatt Arsenault return nullptr; 2173e6f7ab8SMatt Arsenault 218573a5de7SMatt Arsenault // Add/sub are more likely codegen to instructions with carry out side 219573a5de7SMatt Arsenault // effects. 220573a5de7SMatt Arsenault return B.CreateOr(I.getOperand(0), I.getOperand(1)); 221573a5de7SMatt Arsenault } 222573a5de7SMatt Arsenault case Instruction::Call: { 2233e6f7ab8SMatt Arsenault if (IntrinsicInst *II = dyn_cast<IntrinsicInst>(&I)) 2243e6f7ab8SMatt Arsenault return reduceIntrinsic(O, M, II); 225573a5de7SMatt Arsenault 2264638ba7bSMatt Arsenault CallBase *CB = cast<CallBase>(&I); 2274638ba7bSMatt Arsenault 2284638ba7bSMatt Arsenault if (Value *NewOp = tryReplaceCallWithOperator(O, M, CB)) 2294638ba7bSMatt Arsenault return NewOp; 2304638ba7bSMatt Arsenault 2314638ba7bSMatt Arsenault if (Value *NewOp = tryReplaceCallWithLoadStore(O, M, CB)) 2324638ba7bSMatt Arsenault return NewOp; 2334638ba7bSMatt Arsenault 234573a5de7SMatt Arsenault return nullptr; 235573a5de7SMatt Arsenault } 236573a5de7SMatt Arsenault default: 237573a5de7SMatt Arsenault return nullptr; 238573a5de7SMatt Arsenault } 239573a5de7SMatt Arsenault 240573a5de7SMatt Arsenault return nullptr; 241573a5de7SMatt Arsenault } 242573a5de7SMatt Arsenault 24323cc36e4SMatt Arsenault static void replaceOpcodesInModule(Oracle &O, ReducerWorkItem &WorkItem) { 24423cc36e4SMatt Arsenault Module &Mod = WorkItem.getModule(); 24523cc36e4SMatt Arsenault 246573a5de7SMatt Arsenault for (Function &F : Mod) { 247573a5de7SMatt Arsenault for (BasicBlock &BB : F) 248573a5de7SMatt Arsenault for (Instruction &I : make_early_inc_range(BB)) { 249573a5de7SMatt Arsenault Instruction *Replacement = 2503e6f7ab8SMatt Arsenault dyn_cast_or_null<Instruction>(reduceInstruction(O, Mod, I)); 251573a5de7SMatt Arsenault if (Replacement && Replacement != &I) { 252988025adSKazu Hirata if (isa<FPMathOperator>(Replacement)) 253573a5de7SMatt Arsenault Replacement->copyFastMathFlags(&I); 254573a5de7SMatt Arsenault 255573a5de7SMatt Arsenault Replacement->copyIRFlags(&I); 256573a5de7SMatt Arsenault Replacement->copyMetadata(I); 257573a5de7SMatt Arsenault Replacement->takeName(&I); 258573a5de7SMatt Arsenault I.replaceAllUsesWith(Replacement); 259573a5de7SMatt Arsenault I.eraseFromParent(); 260573a5de7SMatt Arsenault } 261573a5de7SMatt Arsenault } 262573a5de7SMatt Arsenault } 263573a5de7SMatt Arsenault } 264573a5de7SMatt Arsenault 265573a5de7SMatt Arsenault void llvm::reduceOpcodesDeltaPass(TestRunner &Test) { 2662592ccdeSArthur Eubanks runDeltaPass(Test, replaceOpcodesInModule, "Reducing Opcodes"); 267573a5de7SMatt Arsenault } 268