| /* |
| * Copyright 2016-2017, The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "GlobalMergePass.h" |
| |
| #include "llvm/IR/Constants.h" |
| #include "llvm/IR/DataLayout.h" |
| #include "llvm/IR/GlobalVariable.h" |
| #include "llvm/IR/IRBuilder.h" |
| #include "llvm/IR/Instructions.h" |
| #include "llvm/IR/Module.h" |
| #include "llvm/Pass.h" |
| #include "llvm/Support/Debug.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| #include "Context.h" |
| #include "RSAllocationUtils.h" |
| |
| #include <functional> |
| |
| #define DEBUG_TYPE "rs2spirv-global-merge" |
| |
| using namespace llvm; |
| |
| namespace rs2spirv { |
| |
| namespace { |
| |
| class GlobalMergePass : public ModulePass { |
| public: |
| static char ID; |
| GlobalMergePass(bool CPU = false) : ModulePass(ID), mForCPU(CPU) {} |
| const char *getPassName() const override { return "GlobalMergePass"; } |
| |
| bool runOnModule(Module &M) override { |
| DEBUG(dbgs() << "RS2SPIRVGlobalMergePass\n"); |
| |
| SmallVector<GlobalVariable *, 8> Globals; |
| if (!collectGlobals(M, Globals)) { |
| return false; // Module not modified. |
| } |
| |
| SmallVector<Type *, 8> Tys; |
| Tys.reserve(Globals.size()); |
| |
| Context &RS2SPIRVCtxt = Context::getInstance(); |
| |
| uint32_t index = 0; |
| for (GlobalVariable *GV : Globals) { |
| Tys.push_back(GV->getValueType()); |
| const char *name = GV->getName().data(); |
| RS2SPIRVCtxt.addExportVarIndex(name, index); |
| index++; |
| } |
| |
| LLVMContext &LLVMCtxt = M.getContext(); |
| |
| StructType *MergedTy = StructType::create(LLVMCtxt, "struct.__GPUBuffer"); |
| MergedTy->setBody(Tys, false); |
| |
| // Size calculation has to consider data layout |
| const DataLayout &DL = M.getDataLayout(); |
| const uint64_t BufferSize = DL.getTypeAllocSize(MergedTy); |
| RS2SPIRVCtxt.setGlobalSize(BufferSize); |
| |
| Type *BufferVarTy = mForCPU ? static_cast<Type *>(PointerType::getUnqual( |
| Type::getInt8Ty(M.getContext()))) |
| : static_cast<Type *>(MergedTy); |
| GlobalVariable *MergedGV = |
| new GlobalVariable(M, BufferVarTy, false, GlobalValue::ExternalLinkage, |
| nullptr, "__GPUBlock"); |
| |
| // For CPU, create a constant struct for initial values, which has each of |
| // its fields initialized to the original value of the corresponding global |
| // variable. |
| // During the script initialization, the driver should copy these initial |
| // values to the global buffer. |
| if (mForCPU) { |
| CreateInitFunction(LLVMCtxt, M, MergedGV, MergedTy, BufferSize, Globals); |
| } |
| |
| const bool forCPU = mForCPU; |
| IntegerType *const Int32Ty = Type::getInt32Ty(LLVMCtxt); |
| ConstantInt *const Zero = ConstantInt::get(Int32Ty, 0); |
| Value *Idx[] = {Zero, nullptr}; |
| |
| auto InstMaker = [forCPU, MergedGV, MergedTy, |
| &Idx](Instruction *InsertBefore) { |
| Value *Base = MergedGV; |
| if (forCPU) { |
| LoadInst *Load = new LoadInst(MergedGV, "", InsertBefore); |
| DEBUG(Load->dump()); |
| Base = new BitCastInst(Load, PointerType::getUnqual(MergedTy), "", |
| InsertBefore); |
| DEBUG(Base->dump()); |
| } |
| GetElementPtrInst *GEP = GetElementPtrInst::CreateInBounds( |
| MergedTy, Base, Idx, "", InsertBefore); |
| DEBUG(GEP->dump()); |
| return GEP; |
| }; |
| |
| for (size_t i = 0, e = Globals.size(); i != e; ++i) { |
| GlobalVariable *G = Globals[i]; |
| Idx[1] = ConstantInt::get(Int32Ty, i); |
| ReplaceAllUsesWithNewInstructions(G, std::cref(InstMaker)); |
| G->eraseFromParent(); |
| } |
| |
| // Return true, as the pass modifies module. |
| return true; |
| } |
| |
| private: |
| // In the User of Value Old, replaces all references of Old with Value New |
| static inline void ReplaceUse(User *U, Value *Old, Value *New) { |
| for (unsigned i = 0, n = U->getNumOperands(); i < n; ++i) { |
| if (U->getOperand(i) == Old) { |
| U->getOperandUse(i) = New; |
| } |
| } |
| } |
| |
| // Replaces each use of V with new instructions created by |
| // funcCreateAndInsert and inserted right before that use. In the cases where |
| // the use is not an instruction, but a constant expression, recursively |
| // replaces that constant expression with a newly constructed equivalent |
| // instruction, before replacing V in that new instruction. |
| static inline void ReplaceAllUsesWithNewInstructions( |
| Value *V, |
| std::function<Instruction *(Instruction *)> funcCreateAndInsert) { |
| SmallVector<User *, 8> Users(V->user_begin(), V->user_end()); |
| for (User *U : Users) { |
| if (Instruction *Inst = dyn_cast<Instruction>(U)) { |
| DEBUG(dbgs() << "\nBefore replacement:\n"); |
| DEBUG(Inst->dump()); |
| DEBUG(dbgs() << "----\n"); |
| |
| ReplaceUse(U, V, funcCreateAndInsert(Inst)); |
| |
| DEBUG(Inst->dump()); |
| } else if (ConstantExpr *CE = dyn_cast<ConstantExpr>(U)) { |
| auto InstMaker([CE, V, &funcCreateAndInsert](Instruction *UserOfU) { |
| Instruction *Inst = CE->getAsInstruction(); |
| Inst->insertBefore(UserOfU); |
| ReplaceUse(Inst, V, funcCreateAndInsert(Inst)); |
| |
| DEBUG(Inst->dump()); |
| return Inst; |
| }); |
| ReplaceAllUsesWithNewInstructions(U, InstMaker); |
| } else { |
| DEBUG(U->dump()); |
| llvm_unreachable("Expecting only Instruction or ConstantExpr"); |
| } |
| } |
| } |
| |
| static inline void |
| CreateInitFunction(LLVMContext &LLVMCtxt, Module &M, GlobalVariable *MergedGV, |
| StructType *MergedTy, const uint64_t BufferSize, |
| const SmallVectorImpl<GlobalVariable *> &Globals) { |
| SmallVector<Constant *, 8> Initializers; |
| Initializers.reserve(Globals.size()); |
| for (size_t i = 0, e = Globals.size(); i != e; ++i) { |
| GlobalVariable *G = Globals[i]; |
| Initializers.push_back(G->getInitializer()); |
| } |
| ArrayRef<Constant *> ArrInit(Initializers.begin(), Initializers.end()); |
| Constant *MergedInitializer = ConstantStruct::get(MergedTy, ArrInit); |
| GlobalVariable *MergedInit = |
| new GlobalVariable(M, MergedTy, true, GlobalValue::InternalLinkage, |
| MergedInitializer, "__GPUBlock0"); |
| |
| Function *UserInit = M.getFunction("init"); |
| // If there is no user-defined init() function, make the new global |
| // initialization function the init(). |
| StringRef FName(UserInit ? ".rsov.global_init" : "init"); |
| Function *Func; |
| FunctionType *FTy = FunctionType::get(Type::getVoidTy(LLVMCtxt), false); |
| Func = Function::Create(FTy, GlobalValue::ExternalLinkage, FName, &M); |
| BasicBlock *Blk = BasicBlock::Create(LLVMCtxt, "entry", Func); |
| IRBuilder<> LLVMIRBuilder(Blk); |
| LoadInst *Load = LLVMIRBuilder.CreateLoad(MergedGV); |
| LLVMIRBuilder.CreateMemCpy(Load, MergedInit, BufferSize, 0); |
| LLVMIRBuilder.CreateRetVoid(); |
| |
| // If there is a user-defined init() function, add a call to the global |
| // initialization function in the beginning of that function. |
| if (UserInit) { |
| BasicBlock &EntryBlk = UserInit->getEntryBlock(); |
| CallInst::Create(Func, {}, "", &EntryBlk.front()); |
| } |
| } |
| |
| bool collectGlobals(Module &M, SmallVectorImpl<GlobalVariable *> &Globals) { |
| for (GlobalVariable &GV : M.globals()) { |
| assert(!GV.hasComdat() && "global variable has a comdat section"); |
| assert(!GV.hasSection() && "global variable has a non-default section"); |
| assert(!GV.isDeclaration() && "global variable is only a declaration"); |
| assert(!GV.isThreadLocal() && "global variable is thread-local"); |
| assert(GV.getType()->getAddressSpace() == 0 && |
| "global variable has non-default address space"); |
| |
| // TODO: Constants accessed by kernels should be handled differently |
| if (GV.isConstant()) { |
| continue; |
| } |
| |
| // Global Allocations are handled differently in separate passes |
| if (isRSAllocation(GV)) { |
| continue; |
| } |
| |
| Globals.push_back(&GV); |
| } |
| |
| return !Globals.empty(); |
| } |
| |
| bool mForCPU; |
| }; |
| |
| } // namespace |
| |
| char GlobalMergePass::ID = 0; |
| |
| ModulePass *createGlobalMergePass(bool CPU) { return new GlobalMergePass(CPU); } |
| |
| } // namespace rs2spirv |