106c3fb27SDimitry Andric //===-- NVPTXCtorDtorLowering.cpp - Handle global ctors and dtors --------===// 206c3fb27SDimitry Andric // 306c3fb27SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 406c3fb27SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 506c3fb27SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 606c3fb27SDimitry Andric // 706c3fb27SDimitry Andric //===----------------------------------------------------------------------===// 806c3fb27SDimitry Andric /// 906c3fb27SDimitry Andric /// \file 1006c3fb27SDimitry Andric /// This pass creates a unified init and fini kernel with the required metadata 1106c3fb27SDimitry Andric //===----------------------------------------------------------------------===// 1206c3fb27SDimitry Andric 1306c3fb27SDimitry Andric #include "NVPTXCtorDtorLowering.h" 145f757f3fSDimitry Andric #include "MCTargetDesc/NVPTXBaseInfo.h" 1506c3fb27SDimitry Andric #include "NVPTX.h" 1606c3fb27SDimitry Andric #include "llvm/ADT/StringExtras.h" 1706c3fb27SDimitry Andric #include "llvm/IR/Constants.h" 1806c3fb27SDimitry Andric #include "llvm/IR/Function.h" 1906c3fb27SDimitry Andric #include "llvm/IR/GlobalVariable.h" 2006c3fb27SDimitry Andric #include "llvm/IR/IRBuilder.h" 2106c3fb27SDimitry Andric #include "llvm/IR/Module.h" 2206c3fb27SDimitry Andric #include "llvm/IR/Value.h" 2306c3fb27SDimitry Andric #include "llvm/Pass.h" 2406c3fb27SDimitry Andric #include "llvm/Support/CommandLine.h" 25*0fca6ea1SDimitry Andric #include "llvm/Support/MD5.h" 2606c3fb27SDimitry Andric #include "llvm/Transforms/Utils/ModuleUtils.h" 2706c3fb27SDimitry Andric 2806c3fb27SDimitry Andric using namespace llvm; 2906c3fb27SDimitry Andric 3006c3fb27SDimitry Andric #define DEBUG_TYPE "nvptx-lower-ctor-dtor" 3106c3fb27SDimitry Andric 3206c3fb27SDimitry Andric static cl::opt<std::string> 3306c3fb27SDimitry Andric GlobalStr("nvptx-lower-global-ctor-dtor-id", 3406c3fb27SDimitry Andric cl::desc("Override unique ID of ctor/dtor globals."), 3506c3fb27SDimitry Andric cl::init(""), cl::Hidden); 3606c3fb27SDimitry Andric 375f757f3fSDimitry Andric static cl::opt<bool> 385f757f3fSDimitry Andric CreateKernels("nvptx-emit-init-fini-kernel", 395f757f3fSDimitry Andric cl::desc("Emit kernels to call ctor/dtor globals."), 405f757f3fSDimitry Andric cl::init(true), cl::Hidden); 415f757f3fSDimitry Andric 4206c3fb27SDimitry Andric namespace { 4306c3fb27SDimitry Andric 4406c3fb27SDimitry Andric static std::string getHash(StringRef Str) { 4506c3fb27SDimitry Andric llvm::MD5 Hasher; 4606c3fb27SDimitry Andric llvm::MD5::MD5Result Hash; 4706c3fb27SDimitry Andric Hasher.update(Str); 4806c3fb27SDimitry Andric Hasher.final(Hash); 4906c3fb27SDimitry Andric return llvm::utohexstr(Hash.low(), /*LowerCase=*/true); 5006c3fb27SDimitry Andric } 5106c3fb27SDimitry Andric 525f757f3fSDimitry Andric static void addKernelMetadata(Module &M, GlobalValue *GV) { 535f757f3fSDimitry Andric llvm::LLVMContext &Ctx = M.getContext(); 545f757f3fSDimitry Andric 555f757f3fSDimitry Andric // Get "nvvm.annotations" metadata node. 565f757f3fSDimitry Andric llvm::NamedMDNode *MD = M.getOrInsertNamedMetadata("nvvm.annotations"); 575f757f3fSDimitry Andric 585f757f3fSDimitry Andric llvm::Metadata *KernelMDVals[] = { 595f757f3fSDimitry Andric llvm::ConstantAsMetadata::get(GV), llvm::MDString::get(Ctx, "kernel"), 605f757f3fSDimitry Andric llvm::ConstantAsMetadata::get( 615f757f3fSDimitry Andric llvm::ConstantInt::get(llvm::Type::getInt32Ty(Ctx), 1))}; 625f757f3fSDimitry Andric 635f757f3fSDimitry Andric // This kernel is only to be called single-threaded. 645f757f3fSDimitry Andric llvm::Metadata *ThreadXMDVals[] = { 655f757f3fSDimitry Andric llvm::ConstantAsMetadata::get(GV), llvm::MDString::get(Ctx, "maxntidx"), 665f757f3fSDimitry Andric llvm::ConstantAsMetadata::get( 675f757f3fSDimitry Andric llvm::ConstantInt::get(llvm::Type::getInt32Ty(Ctx), 1))}; 685f757f3fSDimitry Andric llvm::Metadata *ThreadYMDVals[] = { 695f757f3fSDimitry Andric llvm::ConstantAsMetadata::get(GV), llvm::MDString::get(Ctx, "maxntidy"), 705f757f3fSDimitry Andric llvm::ConstantAsMetadata::get( 715f757f3fSDimitry Andric llvm::ConstantInt::get(llvm::Type::getInt32Ty(Ctx), 1))}; 725f757f3fSDimitry Andric llvm::Metadata *ThreadZMDVals[] = { 735f757f3fSDimitry Andric llvm::ConstantAsMetadata::get(GV), llvm::MDString::get(Ctx, "maxntidz"), 745f757f3fSDimitry Andric llvm::ConstantAsMetadata::get( 755f757f3fSDimitry Andric llvm::ConstantInt::get(llvm::Type::getInt32Ty(Ctx), 1))}; 765f757f3fSDimitry Andric 775f757f3fSDimitry Andric llvm::Metadata *BlockMDVals[] = { 785f757f3fSDimitry Andric llvm::ConstantAsMetadata::get(GV), 795f757f3fSDimitry Andric llvm::MDString::get(Ctx, "maxclusterrank"), 805f757f3fSDimitry Andric llvm::ConstantAsMetadata::get( 815f757f3fSDimitry Andric llvm::ConstantInt::get(llvm::Type::getInt32Ty(Ctx), 1))}; 825f757f3fSDimitry Andric 835f757f3fSDimitry Andric // Append metadata to nvvm.annotations. 845f757f3fSDimitry Andric MD->addOperand(llvm::MDNode::get(Ctx, KernelMDVals)); 855f757f3fSDimitry Andric MD->addOperand(llvm::MDNode::get(Ctx, ThreadXMDVals)); 865f757f3fSDimitry Andric MD->addOperand(llvm::MDNode::get(Ctx, ThreadYMDVals)); 875f757f3fSDimitry Andric MD->addOperand(llvm::MDNode::get(Ctx, ThreadZMDVals)); 885f757f3fSDimitry Andric MD->addOperand(llvm::MDNode::get(Ctx, BlockMDVals)); 895f757f3fSDimitry Andric } 905f757f3fSDimitry Andric 915f757f3fSDimitry Andric static Function *createInitOrFiniKernelFunction(Module &M, bool IsCtor) { 925f757f3fSDimitry Andric StringRef InitOrFiniKernelName = 935f757f3fSDimitry Andric IsCtor ? "nvptx$device$init" : "nvptx$device$fini"; 945f757f3fSDimitry Andric if (M.getFunction(InitOrFiniKernelName)) 955f757f3fSDimitry Andric return nullptr; 965f757f3fSDimitry Andric 975f757f3fSDimitry Andric Function *InitOrFiniKernel = Function::createWithDefaultAttr( 985f757f3fSDimitry Andric FunctionType::get(Type::getVoidTy(M.getContext()), false), 995f757f3fSDimitry Andric GlobalValue::WeakODRLinkage, 0, InitOrFiniKernelName, &M); 1005f757f3fSDimitry Andric addKernelMetadata(M, InitOrFiniKernel); 1015f757f3fSDimitry Andric 1025f757f3fSDimitry Andric return InitOrFiniKernel; 1035f757f3fSDimitry Andric } 1045f757f3fSDimitry Andric 1055f757f3fSDimitry Andric // We create the IR required to call each callback in this section. This is 1065f757f3fSDimitry Andric // equivalent to the following code. Normally, the linker would provide us with 1075f757f3fSDimitry Andric // the definitions of the init and fini array sections. The 'nvlink' linker does 1085f757f3fSDimitry Andric // not do this so initializing these values is done by the runtime. 1095f757f3fSDimitry Andric // 1105f757f3fSDimitry Andric // extern "C" void **__init_array_start = nullptr; 1115f757f3fSDimitry Andric // extern "C" void **__init_array_end = nullptr; 1125f757f3fSDimitry Andric // extern "C" void **__fini_array_start = nullptr; 1135f757f3fSDimitry Andric // extern "C" void **__fini_array_end = nullptr; 1145f757f3fSDimitry Andric // 1155f757f3fSDimitry Andric // using InitCallback = void(); 1165f757f3fSDimitry Andric // using FiniCallback = void(); 1175f757f3fSDimitry Andric // 1185f757f3fSDimitry Andric // void call_init_array_callbacks() { 1195f757f3fSDimitry Andric // for (auto start = __init_array_start; start != __init_array_end; ++start) 1205f757f3fSDimitry Andric // reinterpret_cast<InitCallback *>(*start)(); 1215f757f3fSDimitry Andric // } 1225f757f3fSDimitry Andric // 1235f757f3fSDimitry Andric // void call_init_array_callbacks() { 1245f757f3fSDimitry Andric // size_t fini_array_size = __fini_array_end - __fini_array_start; 1255f757f3fSDimitry Andric // for (size_t i = fini_array_size; i > 0; --i) 1265f757f3fSDimitry Andric // reinterpret_cast<FiniCallback *>(__fini_array_start[i - 1])(); 1275f757f3fSDimitry Andric // } 1285f757f3fSDimitry Andric static void createInitOrFiniCalls(Function &F, bool IsCtor) { 1295f757f3fSDimitry Andric Module &M = *F.getParent(); 1305f757f3fSDimitry Andric LLVMContext &C = M.getContext(); 1315f757f3fSDimitry Andric 1325f757f3fSDimitry Andric IRBuilder<> IRB(BasicBlock::Create(C, "entry", &F)); 1335f757f3fSDimitry Andric auto *LoopBB = BasicBlock::Create(C, "while.entry", &F); 1345f757f3fSDimitry Andric auto *ExitBB = BasicBlock::Create(C, "while.end", &F); 1355f757f3fSDimitry Andric Type *PtrTy = IRB.getPtrTy(llvm::ADDRESS_SPACE_GLOBAL); 1365f757f3fSDimitry Andric 1375f757f3fSDimitry Andric auto *Begin = M.getOrInsertGlobal( 1385f757f3fSDimitry Andric IsCtor ? "__init_array_start" : "__fini_array_start", 1395f757f3fSDimitry Andric PointerType::get(C, 0), [&]() { 1405f757f3fSDimitry Andric auto *GV = new GlobalVariable( 1415f757f3fSDimitry Andric M, PointerType::get(C, 0), 1425f757f3fSDimitry Andric /*isConstant=*/false, GlobalValue::WeakAnyLinkage, 1435f757f3fSDimitry Andric Constant::getNullValue(PointerType::get(C, 0)), 1445f757f3fSDimitry Andric IsCtor ? "__init_array_start" : "__fini_array_start", 1455f757f3fSDimitry Andric /*InsertBefore=*/nullptr, GlobalVariable::NotThreadLocal, 1465f757f3fSDimitry Andric /*AddressSpace=*/llvm::ADDRESS_SPACE_GLOBAL); 1475f757f3fSDimitry Andric GV->setVisibility(GlobalVariable::ProtectedVisibility); 1485f757f3fSDimitry Andric return GV; 1495f757f3fSDimitry Andric }); 1505f757f3fSDimitry Andric auto *End = M.getOrInsertGlobal( 1515f757f3fSDimitry Andric IsCtor ? "__init_array_end" : "__fini_array_end", PointerType::get(C, 0), 1525f757f3fSDimitry Andric [&]() { 1535f757f3fSDimitry Andric auto *GV = new GlobalVariable( 1545f757f3fSDimitry Andric M, PointerType::get(C, 0), 1555f757f3fSDimitry Andric /*isConstant=*/false, GlobalValue::WeakAnyLinkage, 1565f757f3fSDimitry Andric Constant::getNullValue(PointerType::get(C, 0)), 1575f757f3fSDimitry Andric IsCtor ? "__init_array_end" : "__fini_array_end", 1585f757f3fSDimitry Andric /*InsertBefore=*/nullptr, GlobalVariable::NotThreadLocal, 1595f757f3fSDimitry Andric /*AddressSpace=*/llvm::ADDRESS_SPACE_GLOBAL); 1605f757f3fSDimitry Andric GV->setVisibility(GlobalVariable::ProtectedVisibility); 1615f757f3fSDimitry Andric return GV; 1625f757f3fSDimitry Andric }); 1635f757f3fSDimitry Andric 1645f757f3fSDimitry Andric // The constructor type is suppoed to allow using the argument vectors, but 1655f757f3fSDimitry Andric // for now we just call them with no arguments. 1665f757f3fSDimitry Andric auto *CallBackTy = FunctionType::get(IRB.getVoidTy(), {}); 1675f757f3fSDimitry Andric 1685f757f3fSDimitry Andric // The destructor array must be called in reverse order. Get an expression to 1695f757f3fSDimitry Andric // the end of the array and iterate backwards in that case. 1705f757f3fSDimitry Andric Value *BeginVal = IRB.CreateLoad(Begin->getType(), Begin, "begin"); 1715f757f3fSDimitry Andric Value *EndVal = IRB.CreateLoad(Begin->getType(), End, "stop"); 1725f757f3fSDimitry Andric if (!IsCtor) { 1735f757f3fSDimitry Andric auto *BeginInt = IRB.CreatePtrToInt(BeginVal, IntegerType::getInt64Ty(C)); 1745f757f3fSDimitry Andric auto *EndInt = IRB.CreatePtrToInt(EndVal, IntegerType::getInt64Ty(C)); 1755f757f3fSDimitry Andric auto *SubInst = IRB.CreateSub(EndInt, BeginInt); 1765f757f3fSDimitry Andric auto *Offset = IRB.CreateAShr( 1775f757f3fSDimitry Andric SubInst, ConstantInt::get(IntegerType::getInt64Ty(C), 3), "offset", 1785f757f3fSDimitry Andric /*IsExact=*/true); 1795f757f3fSDimitry Andric auto *ValuePtr = IRB.CreateGEP(PointerType::get(C, 0), BeginVal, 1805f757f3fSDimitry Andric ArrayRef<Value *>({Offset})); 1815f757f3fSDimitry Andric EndVal = BeginVal; 1825f757f3fSDimitry Andric BeginVal = IRB.CreateInBoundsGEP( 1835f757f3fSDimitry Andric PointerType::get(C, 0), ValuePtr, 1845f757f3fSDimitry Andric ArrayRef<Value *>(ConstantInt::get(IntegerType::getInt64Ty(C), -1)), 1855f757f3fSDimitry Andric "start"); 1865f757f3fSDimitry Andric } 1875f757f3fSDimitry Andric IRB.CreateCondBr( 1885f757f3fSDimitry Andric IRB.CreateCmp(IsCtor ? ICmpInst::ICMP_NE : ICmpInst::ICMP_UGT, BeginVal, 1895f757f3fSDimitry Andric EndVal), 1905f757f3fSDimitry Andric LoopBB, ExitBB); 1915f757f3fSDimitry Andric IRB.SetInsertPoint(LoopBB); 1925f757f3fSDimitry Andric auto *CallBackPHI = IRB.CreatePHI(PtrTy, 2, "ptr"); 1935f757f3fSDimitry Andric auto *CallBack = IRB.CreateLoad(IRB.getPtrTy(F.getAddressSpace()), 1945f757f3fSDimitry Andric CallBackPHI, "callback"); 1955f757f3fSDimitry Andric IRB.CreateCall(CallBackTy, CallBack); 1965f757f3fSDimitry Andric auto *NewCallBack = 1975f757f3fSDimitry Andric IRB.CreateConstGEP1_64(PtrTy, CallBackPHI, IsCtor ? 1 : -1, "next"); 1985f757f3fSDimitry Andric auto *EndCmp = IRB.CreateCmp(IsCtor ? ICmpInst::ICMP_EQ : ICmpInst::ICMP_ULT, 1995f757f3fSDimitry Andric NewCallBack, EndVal, "end"); 2005f757f3fSDimitry Andric CallBackPHI->addIncoming(BeginVal, &F.getEntryBlock()); 2015f757f3fSDimitry Andric CallBackPHI->addIncoming(NewCallBack, LoopBB); 2025f757f3fSDimitry Andric IRB.CreateCondBr(EndCmp, ExitBB, LoopBB); 2035f757f3fSDimitry Andric IRB.SetInsertPoint(ExitBB); 2045f757f3fSDimitry Andric IRB.CreateRetVoid(); 2055f757f3fSDimitry Andric } 2065f757f3fSDimitry Andric 2075f757f3fSDimitry Andric static bool createInitOrFiniGlobals(Module &M, GlobalVariable *GV, 20806c3fb27SDimitry Andric bool IsCtor) { 20906c3fb27SDimitry Andric ConstantArray *GA = dyn_cast<ConstantArray>(GV->getInitializer()); 21006c3fb27SDimitry Andric if (!GA || GA->getNumOperands() == 0) 21106c3fb27SDimitry Andric return false; 21206c3fb27SDimitry Andric 21306c3fb27SDimitry Andric // NVPTX has no way to emit variables at specific sections or support for 21406c3fb27SDimitry Andric // the traditional constructor sections. Instead, we emit mangled global 21506c3fb27SDimitry Andric // names so the runtime can build the list manually. 21606c3fb27SDimitry Andric for (Value *V : GA->operands()) { 21706c3fb27SDimitry Andric auto *CS = cast<ConstantStruct>(V); 21806c3fb27SDimitry Andric auto *F = cast<Constant>(CS->getOperand(1)); 21906c3fb27SDimitry Andric uint64_t Priority = cast<ConstantInt>(CS->getOperand(0))->getSExtValue(); 22006c3fb27SDimitry Andric std::string PriorityStr = "." + std::to_string(Priority); 22106c3fb27SDimitry Andric // We append a semi-unique hash and the priority to the global name. 22206c3fb27SDimitry Andric std::string GlobalID = 22306c3fb27SDimitry Andric !GlobalStr.empty() ? GlobalStr : getHash(M.getSourceFileName()); 22406c3fb27SDimitry Andric std::string NameStr = 22506c3fb27SDimitry Andric ((IsCtor ? "__init_array_object_" : "__fini_array_object_") + 22606c3fb27SDimitry Andric F->getName() + "_" + GlobalID + "_" + std::to_string(Priority)) 22706c3fb27SDimitry Andric .str(); 22806c3fb27SDimitry Andric // PTX does not support exported names with '.' in them. 22906c3fb27SDimitry Andric llvm::transform(NameStr, NameStr.begin(), 23006c3fb27SDimitry Andric [](char c) { return c == '.' ? '_' : c; }); 23106c3fb27SDimitry Andric 23206c3fb27SDimitry Andric auto *GV = new GlobalVariable(M, F->getType(), /*IsConstant=*/true, 23306c3fb27SDimitry Andric GlobalValue::ExternalLinkage, F, NameStr, 23406c3fb27SDimitry Andric nullptr, GlobalValue::NotThreadLocal, 23506c3fb27SDimitry Andric /*AddressSpace=*/4); 23606c3fb27SDimitry Andric // This isn't respected by Nvidia, simply put here for clarity. 23706c3fb27SDimitry Andric GV->setSection(IsCtor ? ".init_array" + PriorityStr 23806c3fb27SDimitry Andric : ".fini_array" + PriorityStr); 23906c3fb27SDimitry Andric GV->setVisibility(GlobalVariable::ProtectedVisibility); 24006c3fb27SDimitry Andric appendToUsed(M, {GV}); 24106c3fb27SDimitry Andric } 24206c3fb27SDimitry Andric 2435f757f3fSDimitry Andric return true; 2445f757f3fSDimitry Andric } 2455f757f3fSDimitry Andric 2465f757f3fSDimitry Andric static bool createInitOrFiniKernel(Module &M, StringRef GlobalName, 2475f757f3fSDimitry Andric bool IsCtor) { 2485f757f3fSDimitry Andric GlobalVariable *GV = M.getGlobalVariable(GlobalName); 2495f757f3fSDimitry Andric if (!GV || !GV->hasInitializer()) 2505f757f3fSDimitry Andric return false; 2515f757f3fSDimitry Andric 2525f757f3fSDimitry Andric if (!createInitOrFiniGlobals(M, GV, IsCtor)) 2535f757f3fSDimitry Andric return false; 2545f757f3fSDimitry Andric 2555f757f3fSDimitry Andric if (!CreateKernels) 2565f757f3fSDimitry Andric return true; 2575f757f3fSDimitry Andric 2585f757f3fSDimitry Andric Function *InitOrFiniKernel = createInitOrFiniKernelFunction(M, IsCtor); 2595f757f3fSDimitry Andric if (!InitOrFiniKernel) 2605f757f3fSDimitry Andric return false; 2615f757f3fSDimitry Andric 2625f757f3fSDimitry Andric createInitOrFiniCalls(*InitOrFiniKernel, IsCtor); 2635f757f3fSDimitry Andric 26406c3fb27SDimitry Andric GV->eraseFromParent(); 26506c3fb27SDimitry Andric return true; 26606c3fb27SDimitry Andric } 26706c3fb27SDimitry Andric 26806c3fb27SDimitry Andric static bool lowerCtorsAndDtors(Module &M) { 26906c3fb27SDimitry Andric bool Modified = false; 2705f757f3fSDimitry Andric Modified |= createInitOrFiniKernel(M, "llvm.global_ctors", /*IsCtor =*/true); 2715f757f3fSDimitry Andric Modified |= createInitOrFiniKernel(M, "llvm.global_dtors", /*IsCtor =*/false); 27206c3fb27SDimitry Andric return Modified; 27306c3fb27SDimitry Andric } 27406c3fb27SDimitry Andric 27506c3fb27SDimitry Andric class NVPTXCtorDtorLoweringLegacy final : public ModulePass { 27606c3fb27SDimitry Andric public: 27706c3fb27SDimitry Andric static char ID; 27806c3fb27SDimitry Andric NVPTXCtorDtorLoweringLegacy() : ModulePass(ID) {} 27906c3fb27SDimitry Andric bool runOnModule(Module &M) override { return lowerCtorsAndDtors(M); } 28006c3fb27SDimitry Andric }; 28106c3fb27SDimitry Andric 28206c3fb27SDimitry Andric } // End anonymous namespace 28306c3fb27SDimitry Andric 28406c3fb27SDimitry Andric PreservedAnalyses NVPTXCtorDtorLoweringPass::run(Module &M, 28506c3fb27SDimitry Andric ModuleAnalysisManager &AM) { 28606c3fb27SDimitry Andric return lowerCtorsAndDtors(M) ? PreservedAnalyses::none() 28706c3fb27SDimitry Andric : PreservedAnalyses::all(); 28806c3fb27SDimitry Andric } 28906c3fb27SDimitry Andric 29006c3fb27SDimitry Andric char NVPTXCtorDtorLoweringLegacy::ID = 0; 29106c3fb27SDimitry Andric char &llvm::NVPTXCtorDtorLoweringLegacyPassID = NVPTXCtorDtorLoweringLegacy::ID; 29206c3fb27SDimitry Andric INITIALIZE_PASS(NVPTXCtorDtorLoweringLegacy, DEBUG_TYPE, 29306c3fb27SDimitry Andric "Lower ctors and dtors for NVPTX", false, false) 29406c3fb27SDimitry Andric 29506c3fb27SDimitry Andric ModulePass *llvm::createNVPTXCtorDtorLoweringLegacyPass() { 29606c3fb27SDimitry Andric return new NVPTXCtorDtorLoweringLegacy(); 29706c3fb27SDimitry Andric } 298