| //===- SPIRVLowerOCLBlocks.cpp - Lower OpenCL blocks ------------*- C++ -*-===// |
| // |
| // The LLVM/SPIR-V Translator |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| // Copyright (c) 2014 Advanced Micro Devices, Inc. All rights reserved. |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a |
| // copy of this software and associated documentation files (the "Software"), |
| // to deal with the Software without restriction, including without limitation |
| // the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| // and/or sell copies of the Software, and to permit persons to whom the |
| // Software is furnished to do so, subject to the following conditions: |
| // |
| // Redistributions of source code must retain the above copyright notice, |
| // this list of conditions and the following disclaimers. |
| // Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimers in the documentation |
| // and/or other materials provided with the distribution. |
| // Neither the names of Advanced Micro Devices, Inc., nor the names of its |
| // contributors may be used to endorse or promote products derived from this |
| // Software without specific prior written permission. |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| // CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH |
| // THE SOFTWARE. |
| // |
| //===----------------------------------------------------------------------===// |
| /// \file |
| /// |
| /// This file implements lowering of OpenCL blocks to functions. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef OCLLOWERBLOCKS_H_ |
| #define OCLLOWERBLOCKS_H_ |
| |
| #include "SPIRVInternal.h" |
| #include "OCLUtil.h" |
| |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/SetVector.h" |
| #include "llvm/ADT/StringSwitch.h" |
| #include "llvm/ADT/Triple.h" |
| #include "llvm/Analysis/AliasAnalysis.h" |
| #include "llvm/Analysis/AssumptionCache.h" |
| #include "llvm/Analysis/CallGraph.h" |
| #include "llvm/IR/Verifier.h" |
| #include "llvm/Bitcode/ReaderWriter.h" |
| #include "llvm/IR/Constants.h" |
| #include "llvm/IR/DerivedTypes.h" |
| #include "llvm/IR/Function.h" |
| #include "llvm/IR/InstrTypes.h" |
| #include "llvm/IR/Instructions.h" |
| #include "llvm/IR/Module.h" |
| #include "llvm/IR/Operator.h" |
| #include "llvm/Pass.h" |
| #include "llvm/PassSupport.h" |
| #include "llvm/Support/Casting.h" |
| #include "llvm/Support/Debug.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include "llvm/Support/ToolOutputFile.h" |
| #include "llvm/Transforms/Utils/Cloning.h" |
| |
| #include <iostream> |
| #include <list> |
| #include <memory> |
| #include <set> |
| #include <sstream> |
| #include <vector> |
| |
| #define DEBUG_TYPE "spvblocks" |
| |
| using namespace llvm; |
| using namespace SPIRV; |
| using namespace OCLUtil; |
| |
| namespace SPIRV{ |
| |
| /// Lower SPIR2 blocks to function calls. |
| /// |
| /// SPIR2 representation of blocks: |
| /// |
| /// block = spir_block_bind(bitcast(block_func), context_len, context_align, |
| /// context) |
| /// block_func_ptr = bitcast(spir_get_block_invoke(block)) |
| /// context_ptr = spir_get_block_context(block) |
| /// ret = block_func_ptr(context_ptr, args) |
| /// |
| /// Propagates block_func to each spir_get_block_invoke through def-use chain of |
| /// spir_block_bind, so that |
| /// ret = block_func(context, args) |
| class SPIRVLowerOCLBlocks: public ModulePass { |
| public: |
| SPIRVLowerOCLBlocks():ModulePass(ID), M(nullptr){ |
| initializeSPIRVLowerOCLBlocksPass(*PassRegistry::getPassRegistry()); |
| } |
| |
| virtual void getAnalysisUsage(AnalysisUsage &AU) const { |
| AU.addRequired<CallGraphWrapperPass>(); |
| //AU.addRequired<AliasAnalysis>(); |
| AU.addRequired<AssumptionCacheTracker>(); |
| } |
| |
| virtual bool runOnModule(Module &Module) { |
| M = &Module; |
| lowerBlockBind(); |
| lowerGetBlockInvoke(); |
| lowerGetBlockContext(); |
| erase(M->getFunction(SPIR_INTRINSIC_GET_BLOCK_INVOKE)); |
| erase(M->getFunction(SPIR_INTRINSIC_GET_BLOCK_CONTEXT)); |
| erase(M->getFunction(SPIR_INTRINSIC_BLOCK_BIND)); |
| DEBUG(dbgs() << "------- After OCLLowerBlocks ------------\n" << |
| *M << '\n'); |
| return true; |
| } |
| |
| static char ID; |
| private: |
| const static int MaxIter = 1000; |
| Module *M; |
| |
| bool |
| lowerBlockBind() { |
| auto F = M->getFunction(SPIR_INTRINSIC_BLOCK_BIND); |
| if (!F) |
| return false; |
| int Iter = MaxIter; |
| while(lowerBlockBind(F) && Iter > 0){ |
| Iter--; |
| DEBUG(dbgs() << "-------------- after iteration " << MaxIter - Iter << |
| " --------------\n" << *M << '\n'); |
| } |
| assert(Iter > 0 && "Too many iterations"); |
| return true; |
| } |
| |
| bool |
| eraseUselessFunctions() { |
| bool changed = false; |
| for (auto I = M->begin(), E = M->end(); I != E;) { |
| Function *F = static_cast<Function*>(I++); |
| if (!GlobalValue::isInternalLinkage(F->getLinkage()) && |
| !F->isDeclaration()) |
| continue; |
| |
| dumpUsers(F, "[eraseUselessFunctions] "); |
| for (auto UI = F->user_begin(), UE = F->user_end(); UI != UE;) { |
| auto U = *UI++; |
| if (auto CE = dyn_cast<ConstantExpr>(U)){ |
| if (CE->use_empty()) { |
| CE->dropAllReferences(); |
| changed = true; |
| } |
| } |
| } |
| if (F->use_empty()) { |
| erase(F); |
| changed = true; |
| } |
| } |
| return changed; |
| } |
| |
| void |
| lowerGetBlockInvoke() { |
| if (auto F = M->getFunction(SPIR_INTRINSIC_GET_BLOCK_INVOKE)) { |
| for (auto UI = F->user_begin(), UE = F->user_end(); UI != UE;) { |
| auto CI = dyn_cast<CallInst>(*UI++); |
| assert(CI && "Invalid usage of spir_get_block_invoke"); |
| lowerGetBlockInvoke(CI); |
| } |
| } |
| } |
| |
| void |
| lowerGetBlockContext() { |
| if (auto F = M->getFunction(SPIR_INTRINSIC_GET_BLOCK_CONTEXT)) { |
| for (auto UI = F->user_begin(), UE = F->user_end(); UI != UE;) { |
| auto CI = dyn_cast<CallInst>(*UI++); |
| assert(CI && "Invalid usage of spir_get_block_context"); |
| lowerGetBlockContext(CI); |
| } |
| } |
| } |
| /// Lower calls of spir_block_bind. |
| /// Return true if the Module is changed. |
| bool |
| lowerBlockBind(Function *BlockBindFunc) { |
| bool changed = false; |
| for (auto I = BlockBindFunc->user_begin(), E = BlockBindFunc->user_end(); |
| I != E;) { |
| DEBUG(dbgs() << "[lowerBlockBind] " << **I << '\n'); |
| // Handle spir_block_bind(bitcast(block_func), context_len, |
| // context_align, context) |
| auto CallBlkBind = cast<CallInst>(*I++); |
| Function *InvF = nullptr; |
| Value *Ctx = nullptr; |
| Value *CtxLen = nullptr; |
| Value *CtxAlign = nullptr; |
| getBlockInvokeFuncAndContext(CallBlkBind, &InvF, &Ctx, &CtxLen, |
| &CtxAlign); |
| for (auto II = CallBlkBind->user_begin(), EE = CallBlkBind->user_end(); |
| II != EE;) { |
| auto BlkUser = *II++; |
| SPIRVDBG(dbgs() << " Block user: " << *BlkUser << '\n'); |
| if (auto Ret = dyn_cast<ReturnInst>(BlkUser)) { |
| bool Inlined = false; |
| changed |= lowerReturnBlock(Ret, CallBlkBind, Inlined); |
| if (Inlined) |
| return true; |
| } else if (auto CI = dyn_cast<CallInst>(BlkUser)){ |
| auto CallBindF = CI->getCalledFunction(); |
| auto Name = CallBindF->getName(); |
| std::string DemangledName; |
| if (Name == SPIR_INTRINSIC_GET_BLOCK_INVOKE) { |
| assert(CI->getArgOperand(0) == CallBlkBind); |
| changed |= lowerGetBlockInvoke(CI, cast<Function>(InvF)); |
| } else if (Name == SPIR_INTRINSIC_GET_BLOCK_CONTEXT) { |
| assert(CI->getArgOperand(0) == CallBlkBind); |
| // Handle context_ptr = spir_get_block_context(block) |
| lowerGetBlockContext(CI, Ctx); |
| changed = true; |
| } else if (oclIsBuiltin(Name, &DemangledName)) { |
| lowerBlockBuiltin(CI, InvF, Ctx, CtxLen, CtxAlign, DemangledName); |
| changed = true; |
| } else |
| llvm_unreachable("Invalid block user"); |
| } |
| } |
| erase(CallBlkBind); |
| } |
| changed |= eraseUselessFunctions(); |
| return changed; |
| } |
| |
| void |
| lowerGetBlockContext(CallInst *CallGetBlkCtx, Value *Ctx = nullptr) { |
| if (!Ctx) |
| getBlockInvokeFuncAndContext(CallGetBlkCtx->getArgOperand(0), nullptr, |
| &Ctx); |
| CallGetBlkCtx->replaceAllUsesWith(Ctx); |
| DEBUG(dbgs() << " [lowerGetBlockContext] " << *CallGetBlkCtx << " => " << |
| *Ctx << "\n\n"); |
| erase(CallGetBlkCtx); |
| } |
| |
| bool |
| lowerGetBlockInvoke(CallInst *CallGetBlkInvoke, |
| Function *InvokeF = nullptr) { |
| bool changed = false; |
| for (auto UI = CallGetBlkInvoke->user_begin(), |
| UE = CallGetBlkInvoke->user_end(); |
| UI != UE;) { |
| // Handle block_func_ptr = bitcast(spir_get_block_invoke(block)) |
| auto CallInv = cast<Instruction>(*UI++); |
| auto Cast = dyn_cast<BitCastInst>(CallInv); |
| if (Cast) |
| CallInv = dyn_cast<Instruction>(*CallInv->user_begin()); |
| DEBUG(dbgs() << "[lowerGetBlockInvoke] " << *CallInv); |
| // Handle ret = block_func_ptr(context_ptr, args) |
| auto CI = cast<CallInst>(CallInv); |
| auto F = CI->getCalledValue(); |
| if (InvokeF == nullptr) { |
| getBlockInvokeFuncAndContext(CallGetBlkInvoke->getArgOperand(0), |
| &InvokeF, nullptr); |
| assert(InvokeF); |
| } |
| assert(F->getType() == InvokeF->getType()); |
| CI->replaceUsesOfWith(F, InvokeF); |
| DEBUG(dbgs() << " => " << *CI << "\n\n"); |
| erase(Cast); |
| changed = true; |
| } |
| erase(CallGetBlkInvoke); |
| return changed; |
| } |
| |
| void |
| lowerBlockBuiltin(CallInst *CI, Function *InvF, Value *Ctx, Value *CtxLen, |
| Value *CtxAlign, const std::string& DemangledName) { |
| mutateCallInstSPIRV (M, CI, [=](CallInst *CI, std::vector<Value *> &Args) { |
| size_t I = 0; |
| size_t E = Args.size(); |
| for (; I != E; ++I) { |
| if (isPointerToOpaqueStructType(Args[I]->getType(), |
| SPIR_TYPE_NAME_BLOCK_T)) { |
| break; |
| } |
| } |
| assert (I < E); |
| Args[I] = castToVoidFuncPtr(InvF); |
| if (I + 1 == E) { |
| Args.push_back(Ctx); |
| Args.push_back(CtxLen); |
| Args.push_back(CtxAlign); |
| } else { |
| Args.insert(Args.begin() + I + 1, CtxAlign); |
| Args.insert(Args.begin() + I + 1, CtxLen); |
| Args.insert(Args.begin() + I + 1, Ctx); |
| } |
| if (DemangledName == kOCLBuiltinName::EnqueueKernel) { |
| // Insert event arguments if there are not. |
| if (!isa<IntegerType>(Args[3]->getType())) { |
| Args.insert(Args.begin() + 3, getInt32(M, 0)); |
| Args.insert(Args.begin() + 4, getOCLNullClkEventPtr()); |
| } |
| if (!isOCLClkEventPtrType(Args[5]->getType())) |
| Args.insert(Args.begin() + 5, getOCLNullClkEventPtr()); |
| } |
| return getSPIRVFuncName(OCLSPIRVBuiltinMap::map(DemangledName)); |
| }); |
| } |
| /// Transform return of a block. |
| /// The function returning a block is inlined since the context cannot be |
| /// passed to another function. |
| /// Returns true of module is changed. |
| bool |
| lowerReturnBlock(ReturnInst *Ret, Value *CallBlkBind, bool &Inlined) { |
| auto F = Ret->getParent()->getParent(); |
| auto changed = false; |
| for (auto UI = F->user_begin(), UE = F->user_end(); UI != UE;) { |
| auto U = *UI++; |
| dumpUsers(U); |
| auto Inst = dyn_cast<Instruction>(U); |
| if (Inst && Inst->use_empty()) { |
| erase(Inst); |
| changed = true; |
| continue; |
| } |
| auto CI = dyn_cast<CallInst>(U); |
| if(!CI || CI->getCalledFunction() != F) |
| continue; |
| |
| DEBUG(dbgs() << "[lowerReturnBlock] inline " << F->getName() << '\n'); |
| auto CG = &getAnalysis<CallGraphWrapperPass>().getCallGraph(); |
| auto ACT = &getAnalysis<AssumptionCacheTracker>(); |
| //auto AA = &getAnalysis<AliasAnalysis>(); |
| //InlineFunctionInfo IFI(CG, M->getDataLayout(), AA, ACT); |
| InlineFunctionInfo IFI(CG, ACT); |
| InlineFunction(CI, IFI); |
| Inlined = true; |
| } |
| return changed || Inlined; |
| } |
| |
| void |
| getBlockInvokeFuncAndContext(Value *Blk, Function **PInvF, Value **PCtx, |
| Value **PCtxLen = nullptr, Value **PCtxAlign = nullptr){ |
| Function *InvF = nullptr; |
| Value *Ctx = nullptr; |
| Value *CtxLen = nullptr; |
| Value *CtxAlign = nullptr; |
| if (auto CallBlkBind = dyn_cast<CallInst>(Blk)) { |
| assert(CallBlkBind->getCalledFunction()->getName() == |
| SPIR_INTRINSIC_BLOCK_BIND && "Invalid block"); |
| InvF = dyn_cast<Function>( |
| CallBlkBind->getArgOperand(0)->stripPointerCasts()); |
| CtxLen = CallBlkBind->getArgOperand(1); |
| CtxAlign = CallBlkBind->getArgOperand(2); |
| Ctx = CallBlkBind->getArgOperand(3); |
| } else if (auto F = dyn_cast<Function>(Blk->stripPointerCasts())) { |
| InvF = F; |
| Ctx = Constant::getNullValue(IntegerType::getInt8PtrTy(M->getContext())); |
| } else if (auto Load = dyn_cast<LoadInst>(Blk)) { |
| auto Op = Load->getPointerOperand(); |
| if (auto GV = dyn_cast<GlobalVariable>(Op)) { |
| if (GV->isConstant()) { |
| InvF = cast<Function>(GV->getInitializer()->stripPointerCasts()); |
| Ctx = Constant::getNullValue(IntegerType::getInt8PtrTy(M->getContext())); |
| } else { |
| llvm_unreachable("load non-constant block?"); |
| } |
| } else { |
| llvm_unreachable("Loading block from non global?"); |
| } |
| } else { |
| llvm_unreachable("Invalid block"); |
| } |
| DEBUG(dbgs() << " Block invocation func: " << InvF->getName() << '\n' << |
| " Block context: " << *Ctx << '\n'); |
| assert(InvF && Ctx && "Invalid block"); |
| if (PInvF) |
| *PInvF = InvF; |
| if (PCtx) |
| *PCtx = Ctx; |
| if (PCtxLen) |
| *PCtxLen = CtxLen; |
| if (PCtxAlign) |
| *PCtxAlign = CtxAlign; |
| } |
| void |
| erase(Instruction *I) { |
| if (!I) |
| return; |
| if (I->use_empty()) { |
| I->dropAllReferences(); |
| I->eraseFromParent(); |
| } |
| else |
| dumpUsers(I); |
| } |
| void |
| erase(ConstantExpr *I) { |
| if (!I) |
| return; |
| if (I->use_empty()) { |
| I->dropAllReferences(); |
| I->destroyConstant(); |
| } else |
| dumpUsers(I); |
| } |
| void |
| erase(Function *F) { |
| if (!F) |
| return; |
| if (!F->use_empty()) { |
| dumpUsers(F); |
| return; |
| } |
| F->dropAllReferences(); |
| auto &CG = getAnalysis<CallGraphWrapperPass>().getCallGraph(); |
| CG.removeFunctionFromModule(new CallGraphNode(F)); |
| } |
| |
| llvm::PointerType* getOCLClkEventType() { |
| return getOrCreateOpaquePtrType(M, SPIR_TYPE_NAME_CLK_EVENT_T, |
| SPIRAS_Global); |
| } |
| |
| llvm::PointerType* getOCLClkEventPtrType() { |
| return PointerType::get(getOCLClkEventType(), SPIRAS_Generic); |
| } |
| |
| bool isOCLClkEventPtrType(Type *T) { |
| if (auto PT = dyn_cast<PointerType>(T)) |
| return isPointerToOpaqueStructType( |
| PT->getElementType(), SPIR_TYPE_NAME_CLK_EVENT_T); |
| return false; |
| } |
| |
| llvm::Constant* getOCLNullClkEventPtr() { |
| return Constant::getNullValue(getOCLClkEventPtrType()); |
| } |
| |
| void dumpGetBlockInvokeUsers(StringRef Prompt) { |
| DEBUG(dbgs() << Prompt); |
| dumpUsers(M->getFunction(SPIR_INTRINSIC_GET_BLOCK_INVOKE)); |
| } |
| }; |
| |
| char SPIRVLowerOCLBlocks::ID = 0; |
| } |
| |
| INITIALIZE_PASS_BEGIN(SPIRVLowerOCLBlocks, "spvblocks", |
| "SPIR-V lower OCL blocks", false, false) |
| INITIALIZE_PASS_DEPENDENCY(CallGraphWrapperPass) |
| INITIALIZE_PASS_DEPENDENCY(AssumptionCacheTracker) |
| //INITIALIZE_AG_DEPENDENCY(AliasAnalysis) |
| INITIALIZE_PASS_END(SPIRVLowerOCLBlocks, "spvblocks", |
| "SPIR-V lower OCL blocks", false, false) |
| |
| ModulePass *llvm::createSPIRVLowerOCLBlocks() { |
| return new SPIRVLowerOCLBlocks(); |
| } |
| |
| #endif /* OCLLOWERBLOCKS_H_ */ |