|  | //===- ReduceOpcodes.cpp - Specialized Delta Pass -------------------------===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  | // | 
|  | // Try to replace instructions that are likely to codegen to simpler or smaller | 
|  | // sequences. This is a fuzzy and target specific concept. | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "ReduceOpcodes.h" | 
|  | #include "llvm/IR/IRBuilder.h" | 
|  | #include "llvm/IR/Instructions.h" | 
|  | #include "llvm/IR/IntrinsicInst.h" | 
|  | #include "llvm/IR/Intrinsics.h" | 
|  | #include "llvm/IR/IntrinsicsAMDGPU.h" | 
|  |  | 
|  | using namespace llvm; | 
|  |  | 
|  | // Assume outgoing undef arguments aren't relevant. | 
|  | // TODO: Maybe skip any trivial constant arguments. | 
|  | static bool shouldIgnoreArgument(const Value *V) { | 
|  | return isa<UndefValue>(V); | 
|  | } | 
|  |  | 
|  | static Value *replaceIntrinsic(Module &M, IntrinsicInst *II, | 
|  | Intrinsic::ID NewIID, | 
|  | ArrayRef<Type *> Tys = {}) { | 
|  | Function *NewFunc = Intrinsic::getOrInsertDeclaration(&M, NewIID, Tys); | 
|  | II->setCalledFunction(NewFunc); | 
|  | return II; | 
|  | } | 
|  |  | 
|  | static Value *reduceIntrinsic(Oracle &O, Module &M, IntrinsicInst *II) { | 
|  | IRBuilder<> B(II); | 
|  | switch (II->getIntrinsicID()) { | 
|  | case Intrinsic::sqrt: | 
|  | if (O.shouldKeep()) | 
|  | return nullptr; | 
|  |  | 
|  | return B.CreateFMul(II->getArgOperand(0), | 
|  | ConstantFP::get(II->getType(), 2.0)); | 
|  | case Intrinsic::minnum: | 
|  | case Intrinsic::maxnum: | 
|  | case Intrinsic::minimum: | 
|  | case Intrinsic::maximum: | 
|  | case Intrinsic::amdgcn_fmul_legacy: | 
|  | if (O.shouldKeep()) | 
|  | return nullptr; | 
|  | return B.CreateFMul(II->getArgOperand(0), II->getArgOperand(1)); | 
|  | case Intrinsic::amdgcn_workitem_id_y: | 
|  | case Intrinsic::amdgcn_workitem_id_z: | 
|  | if (O.shouldKeep()) | 
|  | return nullptr; | 
|  | return replaceIntrinsic(M, II, Intrinsic::amdgcn_workitem_id_x); | 
|  | case Intrinsic::amdgcn_workgroup_id_y: | 
|  | case Intrinsic::amdgcn_workgroup_id_z: | 
|  | if (O.shouldKeep()) | 
|  | return nullptr; | 
|  | return replaceIntrinsic(M, II, Intrinsic::amdgcn_workgroup_id_x); | 
|  | case Intrinsic::amdgcn_div_fixup: | 
|  | case Intrinsic::amdgcn_fma_legacy: | 
|  | if (O.shouldKeep()) | 
|  | return nullptr; | 
|  | return replaceIntrinsic(M, II, Intrinsic::fma, {II->getType()}); | 
|  | default: | 
|  | return nullptr; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Look for calls that look like they could be replaced with a load or store. | 
|  | static bool callLooksLikeLoadStore(CallBase *CB, Value *&DataArg, | 
|  | Value *&PtrArg) { | 
|  | const bool IsStore = CB->getType()->isVoidTy(); | 
|  |  | 
|  | PtrArg = nullptr; | 
|  | DataArg = nullptr; | 
|  | for (Value *Arg : CB->args()) { | 
|  | if (shouldIgnoreArgument(Arg)) | 
|  | continue; | 
|  |  | 
|  | if (!Arg->getType()->isSized()) | 
|  | return false; | 
|  |  | 
|  | if (!PtrArg && Arg->getType()->isPointerTy()) { | 
|  | PtrArg = Arg; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (!IsStore || DataArg) | 
|  | return false; | 
|  |  | 
|  | DataArg = Arg; | 
|  | } | 
|  |  | 
|  | if (IsStore && !DataArg) { | 
|  | // FIXME: For typed pointers, use element type? | 
|  | DataArg = ConstantInt::get(IntegerType::getInt32Ty(CB->getContext()), 0); | 
|  | } | 
|  |  | 
|  | // If we didn't find any arguments, we can fill in the pointer. | 
|  | if (!PtrArg) { | 
|  | unsigned AS = CB->getDataLayout().getAllocaAddrSpace(); | 
|  |  | 
|  | PointerType *PtrTy = PointerType::get(CB->getContext(), AS); | 
|  |  | 
|  | PtrArg = ConstantPointerNull::get(PtrTy); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // TODO: Replace 2 pointer argument calls with memcpy | 
|  | static Value *tryReplaceCallWithLoadStore(Oracle &O, Module &M, CallBase *CB) { | 
|  | Value *PtrArg = nullptr; | 
|  | Value *DataArg = nullptr; | 
|  | if (!callLooksLikeLoadStore(CB, DataArg, PtrArg) || O.shouldKeep()) | 
|  | return nullptr; | 
|  |  | 
|  | IRBuilder<> B(CB); | 
|  | if (DataArg) | 
|  | return B.CreateStore(DataArg, PtrArg, true); | 
|  | return B.CreateLoad(CB->getType(), PtrArg, true); | 
|  | } | 
|  |  | 
|  | static bool callLooksLikeOperator(CallBase *CB, | 
|  | SmallVectorImpl<Value *> &OperatorArgs) { | 
|  | Type *ReturnTy = CB->getType(); | 
|  | if (!ReturnTy->isFirstClassType()) | 
|  | return false; | 
|  |  | 
|  | for (Value *Arg : CB->args()) { | 
|  | if (shouldIgnoreArgument(Arg)) | 
|  | continue; | 
|  |  | 
|  | if (Arg->getType() != ReturnTy) | 
|  | return false; | 
|  |  | 
|  | OperatorArgs.push_back(Arg); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static Value *tryReplaceCallWithOperator(Oracle &O, Module &M, CallBase *CB) { | 
|  | SmallVector<Value *, 4> Arguments; | 
|  |  | 
|  | if (!callLooksLikeOperator(CB, Arguments) || Arguments.size() > 3) | 
|  | return nullptr; | 
|  |  | 
|  | if (O.shouldKeep()) | 
|  | return nullptr; | 
|  |  | 
|  | IRBuilder<> B(CB); | 
|  | if (CB->getType()->isFPOrFPVectorTy()) { | 
|  | switch (Arguments.size()) { | 
|  | case 1: | 
|  | return B.CreateFNeg(Arguments[0]); | 
|  | case 2: | 
|  | return B.CreateFMul(Arguments[0], Arguments[1]); | 
|  | case 3: | 
|  | return B.CreateIntrinsic(Intrinsic::fma, {CB->getType()}, Arguments); | 
|  | default: | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | llvm_unreachable("all argument sizes handled"); | 
|  | } | 
|  |  | 
|  | if (CB->getType()->isIntOrIntVectorTy()) { | 
|  | switch (Arguments.size()) { | 
|  | case 1: | 
|  | return B.CreateUnaryIntrinsic(Intrinsic::bswap, Arguments[0]); | 
|  | case 2: | 
|  | return B.CreateAnd(Arguments[0], Arguments[1]); | 
|  | case 3: | 
|  | return B.CreateIntrinsic(Intrinsic::fshl, {CB->getType()}, Arguments); | 
|  | default: | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | llvm_unreachable("all argument sizes handled"); | 
|  | } | 
|  |  | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | static Value *reduceInstruction(Oracle &O, Module &M, Instruction &I) { | 
|  | IRBuilder<> B(&I); | 
|  |  | 
|  | // TODO: fp binary operator with constant to fneg | 
|  | switch (I.getOpcode()) { | 
|  | case Instruction::FDiv: | 
|  | case Instruction::FRem: | 
|  | if (O.shouldKeep()) | 
|  | return nullptr; | 
|  |  | 
|  | // Divisions tends to codegen into a long sequence or a library call. | 
|  | return B.CreateFMul(I.getOperand(0), I.getOperand(1)); | 
|  | case Instruction::UDiv: | 
|  | case Instruction::SDiv: | 
|  | case Instruction::URem: | 
|  | case Instruction::SRem: | 
|  | if (O.shouldKeep()) | 
|  | return nullptr; | 
|  |  | 
|  | // Divisions tends to codegen into a long sequence or a library call. | 
|  | return B.CreateMul(I.getOperand(0), I.getOperand(1)); | 
|  | case Instruction::Add: | 
|  | case Instruction::Sub: { | 
|  | if (O.shouldKeep()) | 
|  | return nullptr; | 
|  |  | 
|  | // Add/sub are more likely codegen to instructions with carry out side | 
|  | // effects. | 
|  | return B.CreateOr(I.getOperand(0), I.getOperand(1)); | 
|  | } | 
|  | case Instruction::Call: { | 
|  | if (IntrinsicInst *II = dyn_cast<IntrinsicInst>(&I)) | 
|  | return reduceIntrinsic(O, M, II); | 
|  |  | 
|  | CallBase *CB = cast<CallBase>(&I); | 
|  |  | 
|  | if (Value *NewOp = tryReplaceCallWithOperator(O, M, CB)) | 
|  | return NewOp; | 
|  |  | 
|  | if (Value *NewOp = tryReplaceCallWithLoadStore(O, M, CB)) | 
|  | return NewOp; | 
|  |  | 
|  | return nullptr; | 
|  | } | 
|  | default: | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | void llvm::reduceOpcodesDeltaPass(Oracle &O, ReducerWorkItem &WorkItem) { | 
|  | Module &Mod = WorkItem.getModule(); | 
|  |  | 
|  | for (Function &F : Mod) { | 
|  | for (BasicBlock &BB : F) | 
|  | for (Instruction &I : make_early_inc_range(BB)) { | 
|  | Instruction *Replacement = | 
|  | dyn_cast_or_null<Instruction>(reduceInstruction(O, Mod, I)); | 
|  | if (Replacement && Replacement != &I) { | 
|  | if (isa<FPMathOperator>(Replacement)) | 
|  | Replacement->copyFastMathFlags(&I); | 
|  |  | 
|  | Replacement->copyIRFlags(&I); | 
|  | Replacement->copyMetadata(I); | 
|  | Replacement->takeName(&I); | 
|  | I.replaceAllUsesWith(Replacement); | 
|  | I.eraseFromParent(); | 
|  | } | 
|  | } | 
|  | } | 
|  | } |