blob: be80d44ccf51cfb8b4a2c50c987091e5ac8be088 [file] [log] [blame]
//===------------ JITLink.h - JIT linker functionality ----------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Contains generic JIT-linker types.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_EXECUTIONENGINE_JITLINK_JITLINK_H
#define LLVM_EXECUTIONENGINE_JITLINK_JITLINK_H
#include "JITLinkMemoryManager.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/Triple.h"
#include "llvm/ExecutionEngine/JITSymbol.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/Memory.h"
#include "llvm/Support/MemoryBuffer.h"
#include <map>
#include <string>
#include <system_error>
namespace llvm {
namespace jitlink {
/// Base class for errors originating in JIT linker, e.g. missing relocation
/// support.
class JITLinkError : public ErrorInfo<JITLinkError> {
public:
static char ID;
JITLinkError(Twine ErrMsg) : ErrMsg(ErrMsg.str()) {}
void log(raw_ostream &OS) const override;
const std::string &getErrorMessage() const { return ErrMsg; }
std::error_code convertToErrorCode() const override;
private:
std::string ErrMsg;
};
// Forward declare the Atom class.
class Atom;
/// Edge class. Represents both object file relocations, as well as layout and
/// keep-alive constraints.
class Edge {
public:
using Kind = uint8_t;
using GenericEdgeKind = enum : Kind {
Invalid, // Invalid edge value.
FirstKeepAlive, // Keeps target alive. Offset/addend zero.
KeepAlive = FirstKeepAlive, // Tag first edge kind that preserves liveness.
LayoutNext, // Layout constraint. Offset/Addend zero.
FirstRelocation // First architecture specific relocation.
};
using OffsetT = uint32_t;
using AddendT = int64_t;
Edge(Kind K, OffsetT Offset, Atom &Target, AddendT Addend)
: Target(&Target), Offset(Offset), Addend(Addend), K(K) {}
OffsetT getOffset() const { return Offset; }
Kind getKind() const { return K; }
void setKind(Kind K) { this->K = K; }
bool isRelocation() const { return K >= FirstRelocation; }
Kind getRelocation() const {
assert(isRelocation() && "Not a relocation edge");
return K - FirstRelocation;
}
bool isKeepAlive() const { return K >= FirstKeepAlive; }
Atom &getTarget() const { return *Target; }
void setTarget(Atom &Target) { this->Target = &Target; }
AddendT getAddend() const { return Addend; }
void setAddend(AddendT Addend) { this->Addend = Addend; }
private:
Atom *Target;
OffsetT Offset;
AddendT Addend;
Kind K = 0;
};
using EdgeVector = std::vector<Edge>;
const StringRef getGenericEdgeKindName(Edge::Kind K);
/// Base Atom class. Used by absolute and undefined atoms.
class Atom {
friend class AtomGraph;
protected:
/// Create a named (as yet unresolved) atom.
Atom(StringRef Name)
: Name(Name), IsDefined(false), IsLive(false), ShouldDiscard(false),
IsGlobal(false), IsAbsolute(false), IsCallable(false),
IsExported(false), IsWeak(false), HasLayoutNext(false),
IsCommon(false) {}
/// Create an absolute symbol atom.
Atom(StringRef Name, JITTargetAddress Address)
: Name(Name), Address(Address), IsDefined(true), IsLive(false),
ShouldDiscard(false), IsGlobal(false), IsAbsolute(false),
IsCallable(false), IsExported(false), IsWeak(false),
HasLayoutNext(false), IsCommon(false) {}
public:
/// Returns true if this atom has a name.
bool hasName() const { return Name != StringRef(); }
/// Returns the name of this atom.
StringRef getName() const { return Name; }
/// Returns the current target address of this atom.
/// The initial target address (for atoms that have one) will be taken from
/// the input object file's virtual address space. During the layout phase
/// of JIT linking the atom's address will be updated to point to its final
/// address in the JIT'd process.
JITTargetAddress getAddress() const { return Address; }
/// Set the current target address of this atom.
void setAddress(JITTargetAddress Address) { this->Address = Address; }
/// Returns true if this is a defined atom.
bool isDefined() const { return IsDefined; }
/// Returns true if this atom is marked as live.
bool isLive() const { return IsLive; }
/// Mark this atom as live.
///
/// Note: Only defined and absolute atoms can be marked live.
void setLive(bool IsLive) {
assert((IsDefined || IsAbsolute || !IsLive) &&
"Only defined and absolute atoms can be marked live");
this->IsLive = IsLive;
}
/// Returns true if this atom should be discarded during pruning.
bool shouldDiscard() const { return ShouldDiscard; }
/// Mark this atom to be discarded.
///
/// Note: Only defined and absolute atoms can be marked live.
void setShouldDiscard(bool ShouldDiscard) {
assert((IsDefined || IsAbsolute || !ShouldDiscard) &&
"Only defined and absolute atoms can be marked live");
this->ShouldDiscard = ShouldDiscard;
}
/// Returns true if this definition is global (i.e. visible outside this
/// linkage unit).
///
/// Note: This is distict from Exported, which means visibile outside the
/// JITDylib that this graph is being linked in to.
bool isGlobal() const { return IsGlobal; }
/// Mark this atom as global.
void setGlobal(bool IsGlobal) { this->IsGlobal = IsGlobal; }
/// Returns true if this atom represents an absolute symbol.
bool isAbsolute() const { return IsAbsolute; }
/// Returns true if this atom is known to be callable.
///
/// Primarily provided for easy interoperability with ORC, which uses the
/// JITSymbolFlags::Common flag to identify symbols that can be interposed
/// with stubs.
bool isCallable() const { return IsCallable; }
/// Mark this atom as callable.
void setCallable(bool IsCallable) {
assert((IsDefined || IsAbsolute || !IsCallable) &&
"Callable atoms must be defined or absolute");
this->IsCallable = IsCallable;
}
/// Returns true if this atom should appear in the symbol table of a final
/// linked image.
bool isExported() const { return IsExported; }
/// Mark this atom as exported.
void setExported(bool IsExported) {
assert((!IsExported || ((IsDefined || IsAbsolute) && hasName())) &&
"Exported atoms must have names");
this->IsExported = IsExported;
}
/// Returns true if this is a weak symbol.
bool isWeak() const { return IsWeak; }
/// Mark this atom as weak.
void setWeak(bool IsWeak) { this->IsWeak = IsWeak; }
private:
StringRef Name;
JITTargetAddress Address = 0;
bool IsDefined : 1;
bool IsLive : 1;
bool ShouldDiscard : 1;
bool IsGlobal : 1;
bool IsAbsolute : 1;
bool IsCallable : 1;
bool IsExported : 1;
bool IsWeak : 1;
protected:
// These flags only make sense for DefinedAtom, but we can minimize the size
// of DefinedAtom by defining them here.
bool HasLayoutNext : 1;
bool IsCommon : 1;
};
// Forward declare DefinedAtom.
class DefinedAtom;
raw_ostream &operator<<(raw_ostream &OS, const Atom &A);
void printEdge(raw_ostream &OS, const Atom &FixupAtom, const Edge &E,
StringRef EdgeKindName);
/// Represents a section address range via a pair of DefinedAtom pointers to
/// the first and last atoms in the section.
class SectionRange {
public:
SectionRange() = default;
SectionRange(DefinedAtom *First, DefinedAtom *Last)
: First(First), Last(Last) {}
DefinedAtom *getFirstAtom() const {
assert((!Last || First) && "First can not be null if end is non-null");
return First;
}
DefinedAtom *getLastAtom() const {
assert((First || !Last) && "Last can not be null if start is non-null");
return Last;
}
bool isEmpty() const {
assert((First || !Last) && "Last can not be null if start is non-null");
return !First;
}
JITTargetAddress getStart() const;
JITTargetAddress getEnd() const;
uint64_t getSize() const;
private:
DefinedAtom *First = nullptr;
DefinedAtom *Last = nullptr;
};
/// Represents an object file section.
class Section {
friend class AtomGraph;
private:
Section(StringRef Name, uint32_t Alignment, sys::Memory::ProtectionFlags Prot,
unsigned Ordinal, bool IsZeroFill)
: Name(Name), Alignment(Alignment), Prot(Prot), Ordinal(Ordinal),
IsZeroFill(IsZeroFill) {
assert(isPowerOf2_32(Alignment) && "Alignments must be a power of 2");
}
using DefinedAtomSet = DenseSet<DefinedAtom *>;
public:
using atom_iterator = DefinedAtomSet::iterator;
using const_atom_iterator = DefinedAtomSet::const_iterator;
~Section();
StringRef getName() const { return Name; }
uint32_t getAlignment() const { return Alignment; }
sys::Memory::ProtectionFlags getProtectionFlags() const { return Prot; }
unsigned getSectionOrdinal() const { return Ordinal; }
size_t getNextAtomOrdinal() { return ++NextAtomOrdinal; }
bool isZeroFill() const { return IsZeroFill; }
/// Returns an iterator over the atoms in the section (in no particular
/// order).
iterator_range<atom_iterator> atoms() {
return make_range(DefinedAtoms.begin(), DefinedAtoms.end());
}
/// Returns an iterator over the atoms in the section (in no particular
/// order).
iterator_range<const_atom_iterator> atoms() const {
return make_range(DefinedAtoms.begin(), DefinedAtoms.end());
}
/// Return the number of atoms in this section.
DefinedAtomSet::size_type atoms_size() { return DefinedAtoms.size(); }
/// Return true if this section contains no atoms.
bool atoms_empty() const { return DefinedAtoms.empty(); }
/// Returns the range of this section as the pair of atoms with the lowest
/// and highest target address. This operation is expensive, as it
/// must traverse all atoms in the section.
///
/// Note: If the section is empty, both values will be null. The section
/// address will evaluate to null, and the size to zero. If the section
/// contains a single atom both values will point to it, the address will
/// evaluate to the address of that atom, and the size will be the size of
/// that atom.
SectionRange getRange() const;
private:
void addAtom(DefinedAtom &DA) {
assert(!DefinedAtoms.count(&DA) && "Atom is already in this section");
DefinedAtoms.insert(&DA);
}
void removeAtom(DefinedAtom &DA) {
assert(DefinedAtoms.count(&DA) && "Atom is not in this section");
DefinedAtoms.erase(&DA);
}
StringRef Name;
uint32_t Alignment = 0;
sys::Memory::ProtectionFlags Prot;
unsigned Ordinal = 0;
unsigned NextAtomOrdinal = 0;
bool IsZeroFill = false;
DefinedAtomSet DefinedAtoms;
};
/// Defined atom class. Suitable for use by defined named and anonymous
/// atoms.
class DefinedAtom : public Atom {
friend class AtomGraph;
private:
DefinedAtom(Section &Parent, JITTargetAddress Address, uint32_t Alignment)
: Atom("", Address), Parent(Parent), Ordinal(Parent.getNextAtomOrdinal()),
Alignment(Alignment) {
assert(isPowerOf2_32(Alignment) && "Alignments must be a power of two");
}
DefinedAtom(Section &Parent, StringRef Name, JITTargetAddress Address,
uint32_t Alignment)
: Atom(Name, Address), Parent(Parent),
Ordinal(Parent.getNextAtomOrdinal()), Alignment(Alignment) {
assert(isPowerOf2_32(Alignment) && "Alignments must be a power of two");
}
public:
using edge_iterator = EdgeVector::iterator;
Section &getSection() const { return Parent; }
uint64_t getSize() const { return Size; }
StringRef getContent() const {
assert(!Parent.isZeroFill() && "Trying to get content for zero-fill atom");
assert(Size <= std::numeric_limits<size_t>::max() &&
"Content size too large");
return {ContentPtr, static_cast<size_t>(Size)};
}
void setContent(StringRef Content) {
assert(!Parent.isZeroFill() && "Calling setContent on zero-fill atom?");
ContentPtr = Content.data();
Size = Content.size();
}
bool isZeroFill() const { return Parent.isZeroFill(); }
void setZeroFill(uint64_t Size) {
assert(Parent.isZeroFill() && !ContentPtr &&
"Can't set zero-fill length of a non zero-fill atom");
this->Size = Size;
}
uint64_t getZeroFillSize() const {
assert(Parent.isZeroFill() &&
"Can't get zero-fill length of a non zero-fill atom");
return Size;
}
uint32_t getAlignment() const { return Alignment; }
bool hasLayoutNext() const { return HasLayoutNext; }
void setLayoutNext(DefinedAtom &Next) {
assert(!HasLayoutNext && "Atom already has layout-next constraint");
HasLayoutNext = true;
Edges.push_back(Edge(Edge::LayoutNext, 0, Next, 0));
}
DefinedAtom &getLayoutNext() {
assert(HasLayoutNext && "Atom does not have a layout-next constraint");
DefinedAtom *Next = nullptr;
for (auto &E : edges())
if (E.getKind() == Edge::LayoutNext) {
assert(E.getTarget().isDefined() &&
"layout-next target atom must be a defined atom");
Next = static_cast<DefinedAtom *>(&E.getTarget());
break;
}
assert(Next && "Missing LayoutNext edge");
return *Next;
}
bool isCommon() const { return IsCommon; }
void addEdge(Edge::Kind K, Edge::OffsetT Offset, Atom &Target,
Edge::AddendT Addend) {
assert(K != Edge::LayoutNext &&
"Layout edges should be added via setLayoutNext");
Edges.push_back(Edge(K, Offset, Target, Addend));
}
iterator_range<edge_iterator> edges() {
return make_range(Edges.begin(), Edges.end());
}
size_t edges_size() const { return Edges.size(); }
bool edges_empty() const { return Edges.empty(); }
unsigned getOrdinal() const { return Ordinal; }
private:
void setCommon(uint64_t Size) {
assert(ContentPtr == 0 && "Atom already has content?");
IsCommon = true;
setZeroFill(Size);
}
EdgeVector Edges;
uint64_t Size = 0;
Section &Parent;
const char *ContentPtr = nullptr;
unsigned Ordinal = 0;
uint32_t Alignment = 0;
};
inline JITTargetAddress SectionRange::getStart() const {
return First ? First->getAddress() : 0;
}
inline JITTargetAddress SectionRange::getEnd() const {
return Last ? Last->getAddress() + Last->getSize() : 0;
}
inline uint64_t SectionRange::getSize() const { return getEnd() - getStart(); }
inline SectionRange Section::getRange() const {
if (atoms_empty())
return SectionRange();
DefinedAtom *First = *DefinedAtoms.begin(), *Last = *DefinedAtoms.begin();
for (auto *DA : atoms()) {
if (DA->getAddress() < First->getAddress())
First = DA;
if (DA->getAddress() > Last->getAddress())
Last = DA;
}
return SectionRange(First, Last);
}
class AtomGraph {
private:
using SectionList = std::vector<std::unique_ptr<Section>>;
using AddressToAtomMap = std::map<JITTargetAddress, DefinedAtom *>;
using NamedAtomMap = DenseMap<StringRef, Atom *>;
using ExternalAtomSet = DenseSet<Atom *>;
public:
using external_atom_iterator = ExternalAtomSet::iterator;
using section_iterator = pointee_iterator<SectionList::iterator>;
using const_section_iterator = pointee_iterator<SectionList::const_iterator>;
template <typename SecItrT, typename AtomItrT, typename T>
class defined_atom_iterator_impl
: public iterator_facade_base<
defined_atom_iterator_impl<SecItrT, AtomItrT, T>,
std::forward_iterator_tag, T> {
public:
defined_atom_iterator_impl() = default;
defined_atom_iterator_impl(SecItrT SI, SecItrT SE)
: SI(SI), SE(SE),
AI(SI != SE ? SI->atoms().begin() : Section::atom_iterator()) {
moveToNextAtomOrEnd();
}
bool operator==(const defined_atom_iterator_impl &RHS) const {
return (SI == RHS.SI) && (AI == RHS.AI);
}
T operator*() const {
assert(AI != SI->atoms().end() && "Dereferencing end?");
return *AI;
}
defined_atom_iterator_impl operator++() {
++AI;
moveToNextAtomOrEnd();
return *this;
}
private:
void moveToNextAtomOrEnd() {
while (SI != SE && AI == SI->atoms().end()) {
++SI;
if (SI == SE)
AI = Section::atom_iterator();
else
AI = SI->atoms().begin();
}
}
SecItrT SI, SE;
AtomItrT AI;
};
using defined_atom_iterator =
defined_atom_iterator_impl<section_iterator, Section::atom_iterator,
DefinedAtom *>;
using const_defined_atom_iterator =
defined_atom_iterator_impl<const_section_iterator,
Section::const_atom_iterator,
const DefinedAtom *>;
AtomGraph(std::string Name, unsigned PointerSize,
support::endianness Endianness)
: Name(std::move(Name)), PointerSize(PointerSize),
Endianness(Endianness) {}
/// Returns the name of this graph (usually the name of the original
/// underlying MemoryBuffer).
const std::string &getName() { return Name; }
/// Returns the pointer size for use in this graph.
unsigned getPointerSize() const { return PointerSize; }
/// Returns the endianness of atom-content in this graph.
support::endianness getEndianness() const { return Endianness; }
/// Create a section with the given name, protection flags, and alignment.
Section &createSection(StringRef Name, uint32_t Alignment,
sys::Memory::ProtectionFlags Prot, bool IsZeroFill) {
std::unique_ptr<Section> Sec(
new Section(Name, Alignment, Prot, Sections.size(), IsZeroFill));
Sections.push_back(std::move(Sec));
return *Sections.back();
}
/// Add an external atom representing an undefined symbol in this graph.
Atom &addExternalAtom(StringRef Name) {
assert(!NamedAtoms.count(Name) && "Duplicate named atom inserted");
Atom *A = reinterpret_cast<Atom *>(
AtomAllocator.Allocate(sizeof(Atom), alignof(Atom)));
new (A) Atom(Name);
ExternalAtoms.insert(A);
NamedAtoms[Name] = A;
return *A;
}
/// Add an external atom representing an absolute symbol.
Atom &addAbsoluteAtom(StringRef Name, JITTargetAddress Addr) {
assert(!NamedAtoms.count(Name) && "Duplicate named atom inserted");
Atom *A = reinterpret_cast<Atom *>(
AtomAllocator.Allocate(sizeof(Atom), alignof(Atom)));
new (A) Atom(Name, Addr);
AbsoluteAtoms.insert(A);
NamedAtoms[Name] = A;
return *A;
}
/// Add an anonymous defined atom to the graph.
///
/// Anonymous atoms have content but no name. They must have an address.
DefinedAtom &addAnonymousAtom(Section &Parent, JITTargetAddress Address,
uint32_t Alignment) {
DefinedAtom *A = reinterpret_cast<DefinedAtom *>(
AtomAllocator.Allocate(sizeof(DefinedAtom), alignof(DefinedAtom)));
new (A) DefinedAtom(Parent, Address, Alignment);
Parent.addAtom(*A);
getAddrToAtomMap()[A->getAddress()] = A;
return *A;
}
/// Add a defined atom to the graph.
///
/// Allocates and constructs a DefinedAtom instance with the given parent,
/// name, address, and alignment.
DefinedAtom &addDefinedAtom(Section &Parent, StringRef Name,
JITTargetAddress Address, uint32_t Alignment) {
assert(!NamedAtoms.count(Name) && "Duplicate named atom inserted");
DefinedAtom *A = reinterpret_cast<DefinedAtom *>(
AtomAllocator.Allocate(sizeof(DefinedAtom), alignof(DefinedAtom)));
new (A) DefinedAtom(Parent, Name, Address, Alignment);
Parent.addAtom(*A);
getAddrToAtomMap()[A->getAddress()] = A;
NamedAtoms[Name] = A;
return *A;
}
/// Add a common symbol atom to the graph.
///
/// Adds a common-symbol atom to the graph with the given parent, name,
/// address, alignment and size.
DefinedAtom &addCommonAtom(Section &Parent, StringRef Name,
JITTargetAddress Address, uint32_t Alignment,
uint64_t Size) {
assert(!NamedAtoms.count(Name) && "Duplicate named atom inserted");
DefinedAtom *A = reinterpret_cast<DefinedAtom *>(
AtomAllocator.Allocate(sizeof(DefinedAtom), alignof(DefinedAtom)));
new (A) DefinedAtom(Parent, Name, Address, Alignment);
A->setCommon(Size);
Parent.addAtom(*A);
NamedAtoms[Name] = A;
return *A;
}
iterator_range<section_iterator> sections() {
return make_range(section_iterator(Sections.begin()),
section_iterator(Sections.end()));
}
/// Returns the section with the given name if it exists, otherwise returns
/// null.
Section *findSectionByName(StringRef Name) {
for (auto &S : sections())
if (S.getName() == Name)
return &S;
return nullptr;
}
iterator_range<external_atom_iterator> external_atoms() {
return make_range(ExternalAtoms.begin(), ExternalAtoms.end());
}
iterator_range<external_atom_iterator> absolute_atoms() {
return make_range(AbsoluteAtoms.begin(), AbsoluteAtoms.end());
}
iterator_range<defined_atom_iterator> defined_atoms() {
return make_range(defined_atom_iterator(Sections.begin(), Sections.end()),
defined_atom_iterator(Sections.end(), Sections.end()));
}
iterator_range<const_defined_atom_iterator> defined_atoms() const {
return make_range(
const_defined_atom_iterator(Sections.begin(), Sections.end()),
const_defined_atom_iterator(Sections.end(), Sections.end()));
}
/// Returns the atom with the given name, which must exist in this graph.
Atom &getAtomByName(StringRef Name) {
auto I = NamedAtoms.find(Name);
assert(I != NamedAtoms.end() && "Name not in NamedAtoms map");
return *I->second;
}
/// Returns the atom with the given name, which must exist in this graph and
/// be a DefinedAtom.
DefinedAtom &getDefinedAtomByName(StringRef Name) {
auto &A = getAtomByName(Name);
assert(A.isDefined() && "Atom is not a defined atom");
return static_cast<DefinedAtom &>(A);
}
/// Search for the given atom by name.
/// Returns the atom (if found) or an error (if no atom with this name
/// exists).
Expected<Atom &> findAtomByName(StringRef Name) {
auto I = NamedAtoms.find(Name);
if (I == NamedAtoms.end())
return make_error<JITLinkError>("No atom named " + Name);
return *I->second;
}
/// Search for the given defined atom by name.
/// Returns the defined atom (if found) or an error (if no atom with this
/// name exists, or if one exists but is not a defined atom).
Expected<DefinedAtom &> findDefinedAtomByName(StringRef Name) {
auto I = NamedAtoms.find(Name);
if (I == NamedAtoms.end())
return make_error<JITLinkError>("No atom named " + Name);
if (!I->second->isDefined())
return make_error<JITLinkError>("Atom " + Name +
" exists but is not a "
"defined atom");
return static_cast<DefinedAtom &>(*I->second);
}
/// Returns the atom covering the given address, or an error if no such atom
/// exists.
///
/// Returns null if no atom exists at the given address.
DefinedAtom *getAtomByAddress(JITTargetAddress Address) {
refreshAddrToAtomCache();
// If there are no defined atoms, bail out early.
if (AddrToAtomCache->empty())
return nullptr;
// Find the atom *after* the given address.
auto I = AddrToAtomCache->upper_bound(Address);
// If this address falls before any known atom, bail out.
if (I == AddrToAtomCache->begin())
return nullptr;
// The atom we're looking for is the one before the atom we found.
--I;
// Otherwise range check the atom that was found.
assert(!I->second->getContent().empty() && "Atom content not set");
if (Address >= I->second->getAddress() + I->second->getContent().size())
return nullptr;
return I->second;
}
/// Like getAtomByAddress, but returns an Error if the given address is not
/// covered by an atom, rather than a null pointer.
Expected<DefinedAtom &> findAtomByAddress(JITTargetAddress Address) {
if (auto *DA = getAtomByAddress(Address))
return *DA;
return make_error<JITLinkError>("No atom at address " +
formatv("{0:x16}", Address));
}
// Remove the given external atom from the graph.
void removeExternalAtom(Atom &A) {
assert(!A.isDefined() && !A.isAbsolute() && "A is not an external atom");
assert(ExternalAtoms.count(&A) && "A is not in the external atoms set");
ExternalAtoms.erase(&A);
A.~Atom();
}
/// Remove the given absolute atom from the graph.
void removeAbsoluteAtom(Atom &A) {
assert(A.isAbsolute() && "A is not an absolute atom");
assert(AbsoluteAtoms.count(&A) && "A is not in the absolute atoms set");
AbsoluteAtoms.erase(&A);
A.~Atom();
}
/// Remove the given defined atom from the graph.
void removeDefinedAtom(DefinedAtom &DA) {
if (AddrToAtomCache) {
assert(AddrToAtomCache->count(DA.getAddress()) &&
"Cache exists, but does not contain atom");
AddrToAtomCache->erase(DA.getAddress());
}
if (DA.hasName()) {
assert(NamedAtoms.count(DA.getName()) && "Named atom not in map");
NamedAtoms.erase(DA.getName());
}
DA.getSection().removeAtom(DA);
DA.~DefinedAtom();
}
/// Invalidate the atom-to-address map.
void invalidateAddrToAtomMap() { AddrToAtomCache = None; }
/// Dump the graph.
///
/// If supplied, the EdgeKindToName function will be used to name edge
/// kinds in the debug output. Otherwise raw edge kind numbers will be
/// displayed.
void dump(raw_ostream &OS,
std::function<StringRef(Edge::Kind)> EdegKindToName =
std::function<StringRef(Edge::Kind)>());
private:
AddressToAtomMap &getAddrToAtomMap() {
refreshAddrToAtomCache();
return *AddrToAtomCache;
}
const AddressToAtomMap &getAddrToAtomMap() const {
refreshAddrToAtomCache();
return *AddrToAtomCache;
}
void refreshAddrToAtomCache() const {
if (!AddrToAtomCache) {
AddrToAtomCache = AddressToAtomMap();
for (auto *DA : defined_atoms())
(*AddrToAtomCache)[DA->getAddress()] = const_cast<DefinedAtom *>(DA);
}
}
// Put the BumpPtrAllocator first so that we don't free any of the atoms in
// it until all of their destructors have been run.
BumpPtrAllocator AtomAllocator;
std::string Name;
unsigned PointerSize;
support::endianness Endianness;
SectionList Sections;
NamedAtomMap NamedAtoms;
ExternalAtomSet ExternalAtoms;
ExternalAtomSet AbsoluteAtoms;
mutable Optional<AddressToAtomMap> AddrToAtomCache;
};
/// A function for mutating AtomGraphs.
using AtomGraphPassFunction = std::function<Error(AtomGraph &)>;
/// A list of atom graph passes.
using AtomGraphPassList = std::vector<AtomGraphPassFunction>;
/// An atom graph pass configuration, consisting of a list of pre-prune,
/// post-prune, and post-fixup passes.
struct PassConfiguration {
/// Pre-prune passes.
///
/// These passes are called on the graph after it is built, and before any
/// atoms have been pruned.
///
/// Notable use cases: Marking atoms live or should-discard.
AtomGraphPassList PrePrunePasses;
/// Post-prune passes.
///
/// These passes are called on the graph after dead and should-discard atoms
/// have been removed, but before fixups are applied.
///
/// Notable use cases: Building GOT, stub, and TLV atoms.
AtomGraphPassList PostPrunePasses;
/// Post-fixup passes.
///
/// These passes are called on the graph after atom contents has been copied
/// to working memory, and fixups applied.
///
/// Notable use cases: Testing and validation.
AtomGraphPassList PostFixupPasses;
};
/// A map of symbol names to resolved addresses.
using AsyncLookupResult = DenseMap<StringRef, JITEvaluatedSymbol>;
/// A function to call with a resolved symbol map (See AsyncLookupResult) or an
/// error if resolution failed.
using JITLinkAsyncLookupContinuation =
std::function<void(Expected<AsyncLookupResult> LR)>;
/// An asynchronous symbol lookup. Performs a search (possibly asynchronously)
/// for the given symbols, calling the given continuation with either the result
/// (if the lookup succeeds), or an error (if the lookup fails).
using JITLinkAsyncLookupFunction =
std::function<void(const DenseSet<StringRef> &Symbols,
JITLinkAsyncLookupContinuation LookupContinuation)>;
/// Holds context for a single jitLink invocation.
class JITLinkContext {
public:
/// Destroy a JITLinkContext.
virtual ~JITLinkContext();
/// Return the MemoryManager to be used for this link.
virtual JITLinkMemoryManager &getMemoryManager() = 0;
/// Returns a StringRef for the object buffer.
/// This method can not be called once takeObjectBuffer has been called.
virtual MemoryBufferRef getObjectBuffer() const = 0;
/// Notify this context that linking failed.
/// Called by JITLink if linking cannot be completed.
virtual void notifyFailed(Error Err) = 0;
/// Called by JITLink to resolve external symbols. This method is passed a
/// lookup continutation which it must call with a result to continue the
/// linking process.
virtual void lookup(const DenseSet<StringRef> &Symbols,
JITLinkAsyncLookupContinuation LookupContinuation) = 0;
/// Called by JITLink once all defined atoms in the graph have been assigned
/// their final memory locations in the target process. At this point he
/// atom graph can be, inspected to build a symbol table however the atom
/// content will not generally have been copied to the target location yet.
virtual void notifyResolved(AtomGraph &G) = 0;
/// Called by JITLink to notify the context that the object has been
/// finalized (i.e. emitted to memory and memory permissions set). If all of
/// this objects dependencies have also been finalized then the code is ready
/// to run.
virtual void
notifyFinalized(std::unique_ptr<JITLinkMemoryManager::Allocation> A) = 0;
/// Called by JITLink prior to linking to determine whether default passes for
/// the target should be added. The default implementation returns true.
/// If subclasses override this method to return false for any target then
/// they are required to fully configure the pass pipeline for that target.
virtual bool shouldAddDefaultTargetPasses(const Triple &TT) const;
/// Returns the mark-live pass to be used for this link. If no pass is
/// returned (the default) then the target-specific linker implementation will
/// choose a conservative default (usually marking all atoms live).
/// This function is only called if shouldAddDefaultTargetPasses returns true,
/// otherwise the JITContext is responsible for adding a mark-live pass in
/// modifyPassConfig.
virtual AtomGraphPassFunction getMarkLivePass(const Triple &TT) const;
/// Called by JITLink to modify the pass pipeline prior to linking.
/// The default version performs no modification.
virtual Error modifyPassConfig(const Triple &TT, PassConfiguration &Config);
};
/// Marks all atoms in a graph live. This can be used as a default, conservative
/// mark-live implementation.
Error markAllAtomsLive(AtomGraph &G);
/// Basic JITLink implementation.
///
/// This function will use sensible defaults for GOT and Stub handling.
void jitLink(std::unique_ptr<JITLinkContext> Ctx);
} // end namespace jitlink
} // end namespace llvm
#endif // LLVM_EXECUTIONENGINE_JITLINK_JITLINK_H