//===- bolt/Rewrite/JITLinkLinker.cpp - BOLTLinker using JITLink ----------===// // // 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 // //===----------------------------------------------------------------------===// #include "bolt/Rewrite/JITLinkLinker.h" #include "bolt/Core/BinaryContext.h" #include "bolt/Core/BinaryData.h" #include "bolt/Core/BinarySection.h" #include "llvm/ExecutionEngine/JITLink/ELF_riscv.h" #include "llvm/ExecutionEngine/JITLink/JITLink.h" #include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" #include "llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h" #include "llvm/Support/Debug.h" #define DEBUG_TYPE "bolt" namespace llvm { namespace bolt { namespace { bool hasSymbols(const jitlink::Block &B) { return llvm::any_of(B.getSection().symbols(), [&B](const auto &S) { return &S->getBlock() == &B; }); } /// Liveness in JITLink is based on symbols so sections that do not contain /// any symbols will always be pruned. This pass adds anonymous symbols to /// needed sections to prevent pruning. Error markSectionsLive(jitlink::LinkGraph &G) { for (auto &Section : G.sections()) { // We only need allocatable sections. if (Section.getMemLifetime() == orc::MemLifetime::NoAlloc) continue; // Skip empty sections. if (JITLinkLinker::sectionSize(Section) == 0) continue; for (auto *Block : Section.blocks()) { // No need to add symbols if it already has some. if (hasSymbols(*Block)) continue; G.addAnonymousSymbol(*Block, /*Offset=*/0, /*Size=*/0, /*IsCallable=*/false, /*IsLive=*/true); } } return jitlink::markAllSymbolsLive(G); } void reassignSectionAddress(jitlink::LinkGraph &LG, const BinarySection &BinSection, uint64_t Address) { auto *JLSection = LG.findSectionByName(BinSection.getSectionID()); assert(JLSection && "cannot find section in LinkGraph"); auto BlockAddress = Address; for (auto *Block : JITLinkLinker::orderedBlocks(*JLSection)) { // FIXME it would seem to make sense to align here. However, in // non-relocation mode, we simply use the original address of functions // which might not be aligned with the minimum alignment used by // BinaryFunction (2). Example failing test when aligning: // bolt/test/X86/addr32.s Block->setAddress(orc::ExecutorAddr(BlockAddress)); BlockAddress += Block->getSize(); } } } // anonymous namespace struct JITLinkLinker::Context : jitlink::JITLinkContext { JITLinkLinker &Linker; JITLinkLinker::SectionsMapper MapSections; Context(JITLinkLinker &Linker, JITLinkLinker::SectionsMapper MapSections) : JITLinkContext(&Linker.Dylib), Linker(Linker), MapSections(MapSections) {} jitlink::JITLinkMemoryManager &getMemoryManager() override { return *Linker.MM; } bool shouldAddDefaultTargetPasses(const Triple &TT) const override { // The default passes manipulate DWARF sections in a way incompatible with // BOLT. // TODO check if we can actually use these passes to remove some of the // DWARF manipulation done in BOLT. return false; } Error modifyPassConfig(jitlink::LinkGraph &G, jitlink::PassConfiguration &Config) override { Config.PrePrunePasses.push_back(markSectionsLive); Config.PostAllocationPasses.push_back([this](auto &G) { MapSections([&G](const BinarySection &Section, uint64_t Address) { reassignSectionAddress(G, Section, Address); }); return Error::success(); }); if (G.getTargetTriple().isRISCV()) { Config.PostAllocationPasses.push_back( jitlink::createRelaxationPass_ELF_riscv()); } return Error::success(); } void notifyFailed(Error Err) override { errs() << "BOLT-ERROR: JITLink failed: " << Err << '\n'; exit(1); } void lookup(const LookupMap &Symbols, std::unique_ptr LC) override { jitlink::AsyncLookupResult AllResults; for (const auto &Symbol : Symbols) { std::string SymName = (*Symbol.first).str(); LLVM_DEBUG(dbgs() << "BOLT: looking for " << SymName << "\n"); if (auto Address = Linker.lookupSymbol(SymName)) { LLVM_DEBUG(dbgs() << "Resolved to address 0x" << Twine::utohexstr(*Address) << "\n"); AllResults[Symbol.first] = orc::ExecutorSymbolDef( orc::ExecutorAddr(*Address), JITSymbolFlags()); continue; } if (const BinaryData *I = Linker.BC.getBinaryDataByName(SymName)) { uint64_t Address = I->isMoved() && !I->isJumpTable() ? I->getOutputAddress() : I->getAddress(); LLVM_DEBUG(dbgs() << "Resolved to address 0x" << Twine::utohexstr(Address) << "\n"); AllResults[Symbol.first] = orc::ExecutorSymbolDef( orc::ExecutorAddr(Address), JITSymbolFlags()); continue; } if (Linker.BC.isGOTSymbol(SymName)) { if (const BinaryData *I = Linker.BC.getGOTSymbol()) { uint64_t Address = I->isMoved() ? I->getOutputAddress() : I->getAddress(); LLVM_DEBUG(dbgs() << "Resolved to address 0x" << Twine::utohexstr(Address) << "\n"); AllResults[Symbol.first] = orc::ExecutorSymbolDef( orc::ExecutorAddr(Address), JITSymbolFlags()); continue; } } LLVM_DEBUG(dbgs() << "Resolved to address 0x0\n"); AllResults[Symbol.first] = orc::ExecutorSymbolDef(orc::ExecutorAddr(0), JITSymbolFlags()); } LC->run(std::move(AllResults)); } Error notifyResolved(jitlink::LinkGraph &G) override { for (auto *Symbol : G.defined_symbols()) { SymbolInfo Info{Symbol->getAddress().getValue(), Symbol->getSize()}; auto Name = Symbol->hasName() ? (*Symbol->getName()).str() : std::string(); Linker.Symtab.insert({std::move(Name), Info}); } return Error::success(); } void notifyFinalized( jitlink::JITLinkMemoryManager::FinalizedAlloc Alloc) override { if (Alloc) Linker.Allocs.push_back(std::move(Alloc)); ++Linker.MM->ObjectsLoaded; } }; JITLinkLinker::JITLinkLinker(BinaryContext &BC, std::unique_ptr MM) : BC(BC), MM(std::move(MM)) {} JITLinkLinker::~JITLinkLinker() { cantFail(MM->deallocate(std::move(Allocs))); } void JITLinkLinker::loadObject(MemoryBufferRef Obj, SectionsMapper MapSections) { auto LG = jitlink::createLinkGraphFromObject(Obj, BC.getSymbolStringPool()); if (auto E = LG.takeError()) { errs() << "BOLT-ERROR: JITLink failed: " << E << '\n'; exit(1); } if ((*LG)->getTargetTriple().getArch() != BC.TheTriple->getArch()) { errs() << "BOLT-ERROR: linking object with arch " << (*LG)->getTargetTriple().getArchName() << " into context with arch " << BC.TheTriple->getArchName() << "\n"; exit(1); } auto Ctx = std::make_unique(*this, MapSections); jitlink::link(std::move(*LG), std::move(Ctx)); } std::optional JITLinkLinker::lookupSymbolInfo(StringRef Name) const { auto It = Symtab.find(Name.data()); if (It == Symtab.end()) return std::nullopt; return It->second; } SmallVector JITLinkLinker::orderedBlocks(const jitlink::Section &Section) { SmallVector Blocks(Section.blocks()); llvm::sort(Blocks, [](const auto *LHS, const auto *RHS) { return LHS->getAddress() < RHS->getAddress(); }); return Blocks; } size_t JITLinkLinker::sectionSize(const jitlink::Section &Section) { size_t Size = 0; for (const auto *Block : orderedBlocks(Section)) { Size = jitlink::alignToBlock(Size, *Block); Size += Block->getSize(); } return Size; } } // namespace bolt } // namespace llvm