//===--------- JITLinkGeneric.cpp - Generic JIT linker utilities ----------===// // // 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 // //===----------------------------------------------------------------------===// // // Generic JITLinker utility class. // //===----------------------------------------------------------------------===// #include "JITLinkGeneric.h" #include "EHFrameSupportImpl.h" #include "llvm/Support/BinaryStreamReader.h" #include "llvm/Support/MemoryBuffer.h" #define DEBUG_TYPE "jitlink" namespace llvm { namespace jitlink { JITLinkerBase::~JITLinkerBase() {} void JITLinkerBase::linkPhase1(std::unique_ptr Self) { // Build the atom graph. if (auto GraphOrErr = buildGraph(Ctx->getObjectBuffer())) G = std::move(*GraphOrErr); else return Ctx->notifyFailed(GraphOrErr.takeError()); assert(G && "Graph should have been created by buildGraph above"); // Prune and optimize the graph. if (auto Err = runPasses(Passes.PrePrunePasses, *G)) return Ctx->notifyFailed(std::move(Err)); LLVM_DEBUG({ dbgs() << "Atom graph \"" << G->getName() << "\" pre-pruning:\n"; dumpGraph(dbgs()); }); prune(*G); LLVM_DEBUG({ dbgs() << "Atom graph \"" << G->getName() << "\" post-pruning:\n"; dumpGraph(dbgs()); }); // Run post-pruning passes. if (auto Err = runPasses(Passes.PostPrunePasses, *G)) return Ctx->notifyFailed(std::move(Err)); // Sort atoms into segments. layOutAtoms(); // Allocate memory for segments. if (auto Err = allocateSegments(Layout)) return Ctx->notifyFailed(std::move(Err)); // Notify client that the defined atoms have been assigned addresses. Ctx->notifyResolved(*G); auto ExternalSymbols = getExternalSymbolNames(); // We're about to hand off ownership of ourself to the continuation. Grab a // pointer to the context so that we can call it to initiate the lookup. // // FIXME: Once callee expressions are defined to be sequenced before argument // expressions (c++17) we can simplify all this to: // // Ctx->lookup(std::move(UnresolvedExternals), // [Self=std::move(Self)](Expected Result) { // Self->linkPhase2(std::move(Self), std::move(Result)); // }); // // FIXME: Use move capture once we have c++14. auto *TmpCtx = Ctx.get(); auto *UnownedSelf = Self.release(); auto Phase2Continuation = [UnownedSelf](Expected LookupResult) { std::unique_ptr Self(UnownedSelf); UnownedSelf->linkPhase2(std::move(Self), std::move(LookupResult)); }; TmpCtx->lookup(std::move(ExternalSymbols), std::move(Phase2Continuation)); } void JITLinkerBase::linkPhase2(std::unique_ptr Self, Expected LR) { // If the lookup failed, bail out. if (!LR) return deallocateAndBailOut(LR.takeError()); // Assign addresses to external atoms. applyLookupResult(*LR); LLVM_DEBUG({ dbgs() << "Atom graph \"" << G->getName() << "\" before copy-and-fixup:\n"; dumpGraph(dbgs()); }); // Copy atom content to working memory and fix up. if (auto Err = copyAndFixUpAllAtoms(Layout, *Alloc)) return deallocateAndBailOut(std::move(Err)); LLVM_DEBUG({ dbgs() << "Atom graph \"" << G->getName() << "\" after copy-and-fixup:\n"; dumpGraph(dbgs()); }); if (auto Err = runPasses(Passes.PostFixupPasses, *G)) return deallocateAndBailOut(std::move(Err)); // FIXME: Use move capture once we have c++14. auto *UnownedSelf = Self.release(); auto Phase3Continuation = [UnownedSelf](Error Err) { std::unique_ptr Self(UnownedSelf); UnownedSelf->linkPhase3(std::move(Self), std::move(Err)); }; Alloc->finalizeAsync(std::move(Phase3Continuation)); } void JITLinkerBase::linkPhase3(std::unique_ptr Self, Error Err) { if (Err) return deallocateAndBailOut(std::move(Err)); Ctx->notifyFinalized(std::move(Alloc)); } Error JITLinkerBase::runPasses(AtomGraphPassList &Passes, AtomGraph &G) { for (auto &P : Passes) if (auto Err = P(G)) return Err; return Error::success(); } void JITLinkerBase::layOutAtoms() { // Group sections by protections, and whether or not they're zero-fill. for (auto &S : G->sections()) { // Skip empty sections. if (S.atoms_empty()) continue; auto &SL = Layout[S.getProtectionFlags()]; if (S.isZeroFill()) SL.ZeroFillSections.push_back(SegmentLayout::SectionLayout(S)); else SL.ContentSections.push_back(SegmentLayout::SectionLayout(S)); } // Sort sections within the layout by ordinal. { auto CompareByOrdinal = [](const SegmentLayout::SectionLayout &LHS, const SegmentLayout::SectionLayout &RHS) { return LHS.S->getSectionOrdinal() < RHS.S->getSectionOrdinal(); }; for (auto &KV : Layout) { auto &SL = KV.second; std::sort(SL.ContentSections.begin(), SL.ContentSections.end(), CompareByOrdinal); std::sort(SL.ZeroFillSections.begin(), SL.ZeroFillSections.end(), CompareByOrdinal); } } // Add atoms to the sections. for (auto &KV : Layout) { auto &SL = KV.second; for (auto *SIList : {&SL.ContentSections, &SL.ZeroFillSections}) { for (auto &SI : *SIList) { // First build the set of layout-heads (i.e. "heads" of layout-next // chains) by copying the section atoms, then eliminating any that // appear as layout-next targets. DenseSet LayoutHeads; for (auto *DA : SI.S->atoms()) LayoutHeads.insert(DA); for (auto *DA : SI.S->atoms()) if (DA->hasLayoutNext()) LayoutHeads.erase(&DA->getLayoutNext()); // Next, sort the layout heads by address order. std::vector OrderedLayoutHeads; OrderedLayoutHeads.reserve(LayoutHeads.size()); for (auto *DA : LayoutHeads) OrderedLayoutHeads.push_back(DA); // Now sort the list of layout heads by address. std::sort(OrderedLayoutHeads.begin(), OrderedLayoutHeads.end(), [](const DefinedAtom *LHS, const DefinedAtom *RHS) { return LHS->getAddress() < RHS->getAddress(); }); // Now populate the SI.Atoms field by appending each of the chains. for (auto *DA : OrderedLayoutHeads) { SI.Atoms.push_back(DA); while (DA->hasLayoutNext()) { auto &Next = DA->getLayoutNext(); SI.Atoms.push_back(&Next); DA = &Next; } } } } } LLVM_DEBUG({ dbgs() << "Segment ordering:\n"; for (auto &KV : Layout) { dbgs() << " Segment " << static_cast(KV.first) << ":\n"; auto &SL = KV.second; for (auto &SIEntry : {std::make_pair(&SL.ContentSections, "content sections"), std::make_pair(&SL.ZeroFillSections, "zero-fill sections")}) { auto &SIList = *SIEntry.first; dbgs() << " " << SIEntry.second << ":\n"; for (auto &SI : SIList) { dbgs() << " " << SI.S->getName() << ":\n"; for (auto *DA : SI.Atoms) dbgs() << " " << *DA << "\n"; } } } }); } Error JITLinkerBase::allocateSegments(const SegmentLayoutMap &Layout) { // Compute segment sizes and allocate memory. LLVM_DEBUG(dbgs() << "JIT linker requesting: { "); JITLinkMemoryManager::SegmentsRequestMap Segments; for (auto &KV : Layout) { auto &Prot = KV.first; auto &SegLayout = KV.second; // Calculate segment content size. size_t SegContentSize = 0; for (auto &SI : SegLayout.ContentSections) { assert(!SI.S->atoms_empty() && "Sections in layout must not be empty"); assert(!SI.Atoms.empty() && "Section layouts must not be empty"); // Bump to section alignment before processing atoms. SegContentSize = alignTo(SegContentSize, SI.S->getAlignment()); for (auto *DA : SI.Atoms) { SegContentSize = alignTo(SegContentSize, DA->getAlignment()); SegContentSize += DA->getSize(); } } // Get segment content alignment. unsigned SegContentAlign = 1; if (!SegLayout.ContentSections.empty()) { auto &FirstContentSection = SegLayout.ContentSections.front(); SegContentAlign = std::max(FirstContentSection.S->getAlignment(), FirstContentSection.Atoms.front()->getAlignment()); } // Calculate segment zero-fill size. uint64_t SegZeroFillSize = 0; for (auto &SI : SegLayout.ZeroFillSections) { assert(!SI.S->atoms_empty() && "Sections in layout must not be empty"); assert(!SI.Atoms.empty() && "Section layouts must not be empty"); // Bump to section alignment before processing atoms. SegZeroFillSize = alignTo(SegZeroFillSize, SI.S->getAlignment()); for (auto *DA : SI.Atoms) { SegZeroFillSize = alignTo(SegZeroFillSize, DA->getAlignment()); SegZeroFillSize += DA->getSize(); } } // Calculate segment zero-fill alignment. uint32_t SegZeroFillAlign = 1; if (!SegLayout.ZeroFillSections.empty()) { auto &FirstZeroFillSection = SegLayout.ZeroFillSections.front(); SegZeroFillAlign = std::max(FirstZeroFillSection.S->getAlignment(), FirstZeroFillSection.Atoms.front()->getAlignment()); } if (SegContentSize == 0) SegContentAlign = SegZeroFillAlign; if (SegContentAlign % SegZeroFillAlign != 0) return make_error("First content atom alignment does not " "accommodate first zero-fill atom " "alignment"); Segments[Prot] = {SegContentSize, SegContentAlign, SegZeroFillSize, SegZeroFillAlign}; LLVM_DEBUG({ dbgs() << (&KV == &*Layout.begin() ? "" : "; ") << static_cast(Prot) << ": " << SegContentSize << " content bytes (alignment " << SegContentAlign << ") + " << SegZeroFillSize << " zero-fill bytes (alignment " << SegZeroFillAlign << ")"; }); } LLVM_DEBUG(dbgs() << " }\n"); if (auto AllocOrErr = Ctx->getMemoryManager().allocate(Segments)) Alloc = std::move(*AllocOrErr); else return AllocOrErr.takeError(); LLVM_DEBUG({ dbgs() << "JIT linker got working memory:\n"; for (auto &KV : Layout) { auto Prot = static_cast(KV.first); dbgs() << " " << Prot << ": " << (const void *)Alloc->getWorkingMemory(Prot).data() << "\n"; } }); // Update atom target addresses. for (auto &KV : Layout) { auto &Prot = KV.first; auto &SL = KV.second; JITTargetAddress AtomTargetAddr = Alloc->getTargetMemory(static_cast(Prot)); for (auto *SIList : {&SL.ContentSections, &SL.ZeroFillSections}) for (auto &SI : *SIList) { AtomTargetAddr = alignTo(AtomTargetAddr, SI.S->getAlignment()); for (auto *DA : SI.Atoms) { AtomTargetAddr = alignTo(AtomTargetAddr, DA->getAlignment()); DA->setAddress(AtomTargetAddr); AtomTargetAddr += DA->getSize(); } } } return Error::success(); } DenseSet JITLinkerBase::getExternalSymbolNames() const { // Identify unresolved external atoms. DenseSet UnresolvedExternals; for (auto *DA : G->external_atoms()) { assert(DA->getAddress() == 0 && "External has already been assigned an address"); assert(DA->getName() != StringRef() && DA->getName() != "" && "Externals must be named"); UnresolvedExternals.insert(DA->getName()); } return UnresolvedExternals; } void JITLinkerBase::applyLookupResult(AsyncLookupResult Result) { for (auto &KV : Result) { Atom &A = G->getAtomByName(KV.first); assert(A.getAddress() == 0 && "Atom already resolved"); A.setAddress(KV.second.getAddress()); } LLVM_DEBUG({ dbgs() << "Externals after applying lookup result:\n"; for (auto *A : G->external_atoms()) dbgs() << " " << A->getName() << ": " << formatv("{0:x16}", A->getAddress()) << "\n"; }); assert(llvm::all_of(G->external_atoms(), [](Atom *A) { return A->getAddress() != 0; }) && "All atoms should have been resolved by this point"); } void JITLinkerBase::deallocateAndBailOut(Error Err) { assert(Err && "Should not be bailing out on success value"); assert(Alloc && "can not call deallocateAndBailOut before allocation"); Ctx->notifyFailed(joinErrors(std::move(Err), Alloc->deallocate())); } void JITLinkerBase::dumpGraph(raw_ostream &OS) { assert(G && "Graph is not set yet"); G->dump(dbgs(), [this](Edge::Kind K) { return getEdgeKindName(K); }); } void prune(AtomGraph &G) { std::vector Worklist; DenseMap> EdgesToUpdate; // Build the initial worklist from all atoms initially live. for (auto *DA : G.defined_atoms()) { if (!DA->isLive() || DA->shouldDiscard()) continue; for (auto &E : DA->edges()) { if (!E.getTarget().isDefined()) continue; auto &EDT = static_cast(E.getTarget()); if (EDT.shouldDiscard()) EdgesToUpdate[&EDT].push_back(&E); else if (E.isKeepAlive() && !EDT.isLive()) Worklist.push_back(&EDT); } } // Propagate live flags to all atoms reachable from the initial live set. while (!Worklist.empty()) { DefinedAtom &NextLive = *Worklist.back(); Worklist.pop_back(); assert(!NextLive.shouldDiscard() && "should-discard nodes should never make it into the worklist"); // If this atom has already been marked as live, or is marked to be // discarded, then skip it. if (NextLive.isLive()) continue; // Otherwise set it as live and add any non-live atoms that it points to // to the worklist. NextLive.setLive(true); for (auto &E : NextLive.edges()) { if (!E.getTarget().isDefined()) continue; auto &EDT = static_cast(E.getTarget()); if (EDT.shouldDiscard()) EdgesToUpdate[&EDT].push_back(&E); else if (E.isKeepAlive() && !EDT.isLive()) Worklist.push_back(&EDT); } } // Collect atoms to remove, then remove them from the graph. std::vector AtomsToRemove; for (auto *DA : G.defined_atoms()) if (DA->shouldDiscard() || !DA->isLive()) AtomsToRemove.push_back(DA); LLVM_DEBUG(dbgs() << "Pruning atoms:\n"); for (auto *DA : AtomsToRemove) { LLVM_DEBUG(dbgs() << " " << *DA << "... "); // Check whether we need to replace this atom with an external atom. // // We replace if all of the following hold: // (1) The atom is marked should-discard, // (2) it has live edges (i.e. edges from live atoms) pointing to it. // // Otherwise we simply delete the atom. G.removeDefinedAtom(*DA); auto EdgesToUpdateItr = EdgesToUpdate.find(DA); if (EdgesToUpdateItr != EdgesToUpdate.end()) { auto &ExternalReplacement = G.addExternalAtom(DA->getName()); for (auto *EdgeToUpdate : EdgesToUpdateItr->second) EdgeToUpdate->setTarget(ExternalReplacement); LLVM_DEBUG(dbgs() << "replaced with " << ExternalReplacement << "\n"); } else LLVM_DEBUG(dbgs() << "deleted\n"); } // Finally, discard any absolute symbols that were marked should-discard. { std::vector AbsoluteAtomsToRemove; for (auto *A : G.absolute_atoms()) if (A->shouldDiscard() || A->isLive()) AbsoluteAtomsToRemove.push_back(A); for (auto *A : AbsoluteAtomsToRemove) G.removeAbsoluteAtom(*A); } } } // end namespace jitlink } // end namespace llvm