llvm-project/llvm/lib/ExecutionEngine/Orc/ObjectLinkingLayer.cpp

383 lines
12 KiB
C++
Raw Normal View History

Initial implementation of JITLink - A replacement for RuntimeDyld. Summary: JITLink is a jit-linker that performs the same high-level task as RuntimeDyld: it parses relocatable object files and makes their contents runnable in a target process. JITLink aims to improve on RuntimeDyld in several ways: (1) A clear design intended to maximize code-sharing while minimizing coupling. RuntimeDyld has been developed in an ad-hoc fashion for a number of years and this had led to intermingling of code for multiple architectures (e.g. in RuntimeDyldELF::processRelocationRef) in a way that makes the code more difficult to read, reason about, extend. JITLink is designed to isolate format and architecture specific code, while still sharing generic code. (2) Support for native code models. RuntimeDyld required the use of large code models (where calls to external functions are made indirectly via registers) for many of platforms due to its restrictive model for stub generation (one "stub" per symbol). JITLink allows arbitrary mutation of the atom graph, allowing both GOT and PLT atoms to be added naturally. (3) Native support for asynchronous linking. JITLink uses asynchronous calls for symbol resolution and finalization: these callbacks are passed a continuation function that they must call to complete the linker's work. This allows for cleaner interoperation with the new concurrent ORC JIT APIs, while still being easily implementable in synchronous style if asynchrony is not needed. To maximise sharing, the design has a hierarchy of common code: (1) Generic atom-graph data structure and algorithms (e.g. dead stripping and | memory allocation) that are intended to be shared by all architectures. | + -- (2) Shared per-format code that utilizes (1), e.g. Generic MachO to | atom-graph parsing. | + -- (3) Architecture specific code that uses (1) and (2). E.g. JITLinkerMachO_x86_64, which adds x86-64 specific relocation support to (2) to build and patch up the atom graph. To support asynchronous symbol resolution and finalization, the callbacks for these operations take continuations as arguments: using JITLinkAsyncLookupContinuation = std::function<void(Expected<AsyncLookupResult> LR)>; using JITLinkAsyncLookupFunction = std::function<void(const DenseSet<StringRef> &Symbols, JITLinkAsyncLookupContinuation LookupContinuation)>; using FinalizeContinuation = std::function<void(Error)>; virtual void finalizeAsync(FinalizeContinuation OnFinalize); In addition to its headline features, JITLink also makes other improvements: - Dead stripping support: symbols that are not used (e.g. redundant ODR definitions) are discarded, and take up no memory in the target process (In contrast, RuntimeDyld supported pointer equality for weak definitions, but the redundant definitions stayed resident in memory). - Improved exception handling support. JITLink provides a much more extensive eh-frame parser than RuntimeDyld, and is able to correctly fix up many eh-frame sections that RuntimeDyld currently (silently) fails on. - More extensive validation and error handling throughout. This initial patch supports linking MachO/x86-64 only. Work on support for other architectures and formats will happen in-tree. Differential Revision: https://reviews.llvm.org/D58704 llvm-svn: 358818
2019-04-21 01:10:34 +08:00
//===------- ObjectLinkingLayer.cpp - JITLink backed ORC ObjectLayer ------===//
//
// 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 "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ExecutionEngine/JITLink/JITLink_EHFrameSupport.h"
#include <vector>
#define DEBUG_TYPE "orc"
using namespace llvm;
using namespace llvm::jitlink;
using namespace llvm::orc;
namespace llvm {
namespace orc {
class ObjectLinkingLayerJITLinkContext final : public JITLinkContext {
public:
ObjectLinkingLayerJITLinkContext(ObjectLinkingLayer &Layer,
MaterializationResponsibility MR,
std::unique_ptr<MemoryBuffer> ObjBuffer)
: Layer(Layer), MR(std::move(MR)), ObjBuffer(std::move(ObjBuffer)) {}
JITLinkMemoryManager &getMemoryManager() override { return Layer.MemMgr; }
MemoryBufferRef getObjectBuffer() const override {
return ObjBuffer->getMemBufferRef();
}
void notifyFailed(Error Err) override {
Layer.getExecutionSession().reportError(std::move(Err));
MR.failMaterialization();
}
void lookup(const DenseSet<StringRef> &Symbols,
JITLinkAsyncLookupContinuation LookupContinuation) override {
JITDylibSearchList SearchOrder;
MR.getTargetJITDylib().withSearchOrderDo(
[&](const JITDylibSearchList &JDs) { SearchOrder = JDs; });
auto &ES = Layer.getExecutionSession();
SymbolNameSet InternedSymbols;
for (auto &S : Symbols)
InternedSymbols.insert(ES.intern(S));
// OnResolve -- De-intern the symbols and pass the result to the linker.
// FIXME: Capture LookupContinuation by move once we have c++14.
auto SharedLookupContinuation =
std::make_shared<JITLinkAsyncLookupContinuation>(
std::move(LookupContinuation));
auto OnResolve = [SharedLookupContinuation](Expected<SymbolMap> Result) {
if (!Result)
(*SharedLookupContinuation)(Result.takeError());
else {
AsyncLookupResult LR;
for (auto &KV : *Result)
LR[*KV.first] = KV.second;
(*SharedLookupContinuation)(std::move(LR));
}
};
ES.lookup(
SearchOrder, std::move(InternedSymbols), std::move(OnResolve),
// OnReady:
[&ES](Error Err) { ES.reportError(std::move(Err)); },
// RegisterDependencies:
[this](const SymbolDependenceMap &Deps) {
registerDependencies(Deps);
});
}
void notifyResolved(AtomGraph &G) override {
auto &ES = Layer.getExecutionSession();
SymbolFlagsMap ExtraSymbolsToClaim;
bool AutoClaim = Layer.AutoClaimObjectSymbols;
SymbolMap InternedResult;
for (auto *DA : G.defined_atoms())
if (DA->hasName() && DA->isGlobal()) {
auto InternedName = ES.intern(DA->getName());
JITSymbolFlags Flags;
if (DA->isExported())
Flags |= JITSymbolFlags::Exported;
if (DA->isWeak())
Flags |= JITSymbolFlags::Weak;
if (DA->isCallable())
Flags |= JITSymbolFlags::Callable;
if (DA->isCommon())
Flags |= JITSymbolFlags::Common;
InternedResult[InternedName] =
JITEvaluatedSymbol(DA->getAddress(), Flags);
if (AutoClaim && !MR.getSymbols().count(InternedName)) {
assert(!ExtraSymbolsToClaim.count(InternedName) &&
"Duplicate symbol to claim?");
ExtraSymbolsToClaim[InternedName] = Flags;
}
}
for (auto *A : G.absolute_atoms())
if (A->hasName()) {
auto InternedName = ES.intern(A->getName());
JITSymbolFlags Flags;
Flags |= JITSymbolFlags::Absolute;
if (A->isWeak())
Flags |= JITSymbolFlags::Weak;
if (A->isCallable())
Flags |= JITSymbolFlags::Callable;
InternedResult[InternedName] =
JITEvaluatedSymbol(A->getAddress(), Flags);
if (AutoClaim && !MR.getSymbols().count(InternedName)) {
assert(!ExtraSymbolsToClaim.count(InternedName) &&
"Duplicate symbol to claim?");
ExtraSymbolsToClaim[InternedName] = Flags;
}
}
if (!ExtraSymbolsToClaim.empty())
if (auto Err = MR.defineMaterializing(ExtraSymbolsToClaim))
return notifyFailed(std::move(Err));
MR.resolve(InternedResult);
if (Layer.NotifyLoaded)
Layer.NotifyLoaded(MR.getVModuleKey());
}
void notifyFinalized(
std::unique_ptr<JITLinkMemoryManager::Allocation> A) override {
if (EHFrameAddr) {
// If there is an eh-frame then try to register it.
if (auto Err = registerEHFrameSection((void *)EHFrameAddr)) {
Layer.getExecutionSession().reportError(std::move(Err));
MR.failMaterialization();
return;
}
}
MR.emit();
Layer.notifyFinalized(
ObjectLinkingLayer::ObjectResources(std::move(A), EHFrameAddr));
}
AtomGraphPassFunction getMarkLivePass(const Triple &TT) const override {
return [this](AtomGraph &G) { return markResponsibilitySymbolsLive(G); };
}
Error modifyPassConfig(const Triple &TT, PassConfiguration &Config) override {
// Add passes to mark duplicate defs as should-discard, and to walk the
// atom graph to build the symbol dependence graph.
Config.PrePrunePasses.push_back(
[this](AtomGraph &G) { return markSymbolsToDiscard(G); });
Config.PostPrunePasses.push_back(
[this](AtomGraph &G) { return computeNamedSymbolDependencies(G); });
Config.PostFixupPasses.push_back(
createEHFrameRecorderPass(TT, EHFrameAddr));
if (Layer.ModifyPassConfig)
Layer.ModifyPassConfig(TT, Config);
return Error::success();
}
private:
using AnonAtomNamedDependenciesMap =
DenseMap<const DefinedAtom *, SymbolNameSet>;
Error markSymbolsToDiscard(AtomGraph &G) {
auto &ES = Layer.getExecutionSession();
for (auto *DA : G.defined_atoms())
if (DA->isWeak() && DA->hasName()) {
auto S = ES.intern(DA->getName());
auto I = MR.getSymbols().find(S);
if (I == MR.getSymbols().end())
DA->setShouldDiscard(true);
}
for (auto *A : G.absolute_atoms())
if (A->isWeak() && A->hasName()) {
auto S = ES.intern(A->getName());
auto I = MR.getSymbols().find(S);
if (I == MR.getSymbols().end())
A->setShouldDiscard(true);
}
return Error::success();
}
Error markResponsibilitySymbolsLive(AtomGraph &G) const {
auto &ES = Layer.getExecutionSession();
for (auto *DA : G.defined_atoms())
if (DA->hasName() &&
MR.getSymbols().count(ES.intern(DA->getName())))
DA->setLive(true);
return Error::success();
}
Error computeNamedSymbolDependencies(AtomGraph &G) {
auto &ES = MR.getTargetJITDylib().getExecutionSession();
auto AnonDeps = computeAnonDeps(G);
for (auto *DA : G.defined_atoms()) {
// Skip anonymous and non-global atoms: we do not need dependencies for
// these.
if (!DA->hasName() || !DA->isGlobal())
continue;
auto DAName = ES.intern(DA->getName());
SymbolNameSet &DADeps = NamedSymbolDeps[DAName];
for (auto &E : DA->edges()) {
auto &TA = E.getTarget();
if (TA.hasName())
DADeps.insert(ES.intern(TA.getName()));
else {
assert(TA.isDefined() && "Anonymous atoms must be defined");
auto &DTA = static_cast<DefinedAtom &>(TA);
auto I = AnonDeps.find(&DTA);
if (I != AnonDeps.end())
for (auto &S : I->second)
DADeps.insert(S);
}
}
}
return Error::success();
}
AnonAtomNamedDependenciesMap computeAnonDeps(AtomGraph &G) {
auto &ES = MR.getTargetJITDylib().getExecutionSession();
AnonAtomNamedDependenciesMap DepMap;
// For all anonymous atoms:
// (1) Add their named dependencies.
// (2) Add them to the worklist for further iteration if they have any
// depend on any other anonymous atoms.
struct WorklistEntry {
WorklistEntry(DefinedAtom *DA, DenseSet<DefinedAtom *> DAAnonDeps)
: DA(DA), DAAnonDeps(std::move(DAAnonDeps)) {}
DefinedAtom *DA = nullptr;
DenseSet<DefinedAtom *> DAAnonDeps;
};
std::vector<WorklistEntry> Worklist;
for (auto *DA : G.defined_atoms())
if (!DA->hasName()) {
auto &DANamedDeps = DepMap[DA];
DenseSet<DefinedAtom *> DAAnonDeps;
for (auto &E : DA->edges()) {
auto &TA = E.getTarget();
if (TA.hasName())
DANamedDeps.insert(ES.intern(TA.getName()));
else {
assert(TA.isDefined() && "Anonymous atoms must be defined");
DAAnonDeps.insert(static_cast<DefinedAtom *>(&TA));
}
}
if (!DAAnonDeps.empty())
Worklist.push_back(WorklistEntry(DA, std::move(DAAnonDeps)));
}
// Loop over all anonymous atoms with anonymous dependencies, propagating
// their respective *named* dependencies. Iterate until we hit a stable
// state.
bool Changed;
do {
Changed = false;
for (auto &WLEntry : Worklist) {
auto *DA = WLEntry.DA;
auto &DANamedDeps = DepMap[DA];
auto &DAAnonDeps = WLEntry.DAAnonDeps;
for (auto *TA : DAAnonDeps) {
auto I = DepMap.find(TA);
if (I != DepMap.end())
for (const auto &S : I->second)
Changed |= DANamedDeps.insert(S).second;
}
}
} while (Changed);
return DepMap;
}
void registerDependencies(const SymbolDependenceMap &QueryDeps) {
for (auto &NamedDepsEntry : NamedSymbolDeps) {
auto &Name = NamedDepsEntry.first;
auto &NameDeps = NamedDepsEntry.second;
SymbolDependenceMap SymbolDeps;
for (const auto &QueryDepsEntry : QueryDeps) {
JITDylib &SourceJD = *QueryDepsEntry.first;
const SymbolNameSet &Symbols = QueryDepsEntry.second;
auto &DepsForJD = SymbolDeps[&SourceJD];
for (const auto &S : Symbols)
if (NameDeps.count(S))
DepsForJD.insert(S);
if (DepsForJD.empty())
SymbolDeps.erase(&SourceJD);
}
MR.addDependencies(Name, SymbolDeps);
}
}
ObjectLinkingLayer &Layer;
MaterializationResponsibility MR;
std::unique_ptr<MemoryBuffer> ObjBuffer;
DenseMap<SymbolStringPtr, SymbolNameSet> NamedSymbolDeps;
JITTargetAddress EHFrameAddr = 0;
};
ObjectLinkingLayer::ObjectLinkingLayer(
ExecutionSession &ES, JITLinkMemoryManager &MemMgr,
NotifyLoadedFunction NotifyLoaded, NotifyEmittedFunction NotifyEmitted,
ModifyPassConfigFunction ModifyPassConfig)
: ObjectLayer(ES), MemMgr(MemMgr), NotifyLoaded(std::move(NotifyLoaded)),
NotifyEmitted(std::move(NotifyEmitted)),
ModifyPassConfig(std::move(ModifyPassConfig)) {}
void ObjectLinkingLayer::emit(MaterializationResponsibility R,
std::unique_ptr<MemoryBuffer> O) {
assert(O && "Object must not be null");
jitLink(llvm::make_unique<ObjectLinkingLayerJITLinkContext>(
*this, std::move(R), std::move(O)));
}
ObjectLinkingLayer::ObjectResources::ObjectResources(
AllocPtr Alloc, JITTargetAddress EHFrameAddr)
: Alloc(std::move(Alloc)), EHFrameAddr(EHFrameAddr) {}
ObjectLinkingLayer::ObjectResources::ObjectResources(ObjectResources &&Other)
: Alloc(std::move(Other.Alloc)), EHFrameAddr(Other.EHFrameAddr) {
Other.EHFrameAddr = 0;
}
ObjectLinkingLayer::ObjectResources &
ObjectLinkingLayer::ObjectResources::operator=(ObjectResources &&Other) {
std::swap(Alloc, Other.Alloc);
std::swap(EHFrameAddr, Other.EHFrameAddr);
return *this;
}
ObjectLinkingLayer::ObjectResources::~ObjectResources() {
const char *ErrBanner =
"ObjectLinkingLayer received error deallocating object resources:";
assert((EHFrameAddr == 0 || Alloc) &&
"Non-null EHFrameAddr must have an associated allocation");
if (EHFrameAddr)
if (auto Err = deregisterEHFrameSection((void *)EHFrameAddr))
logAllUnhandledErrors(std::move(Err), llvm::errs(), ErrBanner);
if (Alloc)
if (auto Err = Alloc->deallocate())
logAllUnhandledErrors(std::move(Err), llvm::errs(), ErrBanner);
}
} // End namespace orc.
} // End namespace llvm.