Skip to content

Commit e50aea5

Browse files
committed
[JITLink][ORC] Major JITLinkMemoryManager refactor.
This commit substantially refactors the JITLinkMemoryManager API to: (1) add asynchronous versions of key operations, (2) give memory manager implementations full control over link graph address layout, (3) enable more efficient tracking of allocated memory, and (4) support "allocation actions" and finalize-lifetime memory. Together these changes provide a more usable API, and enable more powerful and efficient memory manager implementations. To support these changes the JITLinkMemoryManager::Allocation inner class has been split into two new classes: InFlightAllocation, and FinalizedAllocation. The allocate method returns an InFlightAllocation that tracks memory (both working and executor memory) prior to finalization. The finalize method returns a FinalizedAllocation object, and the InFlightAllocation is discarded. Breaking Allocation into InFlightAllocation and FinalizedAllocation allows InFlightAllocation subclassses to be written more naturally, and FinalizedAlloc to be implemented and used efficiently (see (3) below). In addition to the memory manager changes this commit also introduces a new MemProt type to represent memory protections (MemProt replaces use of sys::Memory::ProtectionFlags in JITLink), and a new MemDeallocPolicy type that can be used to indicate when a section should be deallocated (see (4) below). Plugin/pass writers who were using sys::Memory::ProtectionFlags will have to switch to MemProt -- this should be straightworward. Clients with out-of-tree memory managers will need to update their implementations. Clients using in-tree memory managers should mostly be able to ignore it. Major features: (1) More asynchrony: The allocate and deallocate methods are now asynchronous by default, with synchronous convenience wrappers supplied. The asynchronous versions allow clients (including JITLink) to request and deallocate memory without blocking. (2) Improved control over graph address layout: Instead of a SegmentRequestMap, JITLinkMemoryManager::allocate now takes a reference to the LinkGraph to be allocated. The memory manager is responsible for calculating the memory requirements for the graph, and laying out the graph (setting working and executor memory addresses) within the allocated memory. This gives memory managers full control over JIT'd memory layout. For clients that don't need or want this degree of control the new "BasicLayout" utility can be used to get a segment-based view of the graph, similar to the one provided by SegmentRequestMap. Once segment addresses are assigned the BasicLayout::apply method can be used to automatically lay out the graph. (3) Efficient tracking of allocated memory. The FinalizedAlloc type is a wrapper for an ExecutorAddr and requires only 64-bits to store in the controller. The meaning of the address held by the FinalizedAlloc is left up to the memory manager implementation, but the FinalizedAlloc type enforces a requirement that deallocate be called on any non-default values prior to destruction. The deallocate method takes a vector<FinalizedAlloc>, allowing for bulk deallocation of many allocations in a single call. Memory manager implementations will typically store the address of some allocation metadata in the executor in the FinalizedAlloc, as holding this metadata in the executor is often cheaper and may allow for clean deallocation even in failure cases where the connection with the controller is lost. (4) Support for "allocation actions" and finalize-lifetime memory. Allocation actions are pairs (finalize_act, deallocate_act) of JITTargetAddress triples (fn, arg_buffer_addr, arg_buffer_size), that can be attached to a finalize request. At finalization time, after memory protections have been applied, each of the "finalize_act" elements will be called in order (skipping any elements whose fn value is zero) as ((char*(*)(const char *, size_t))fn)((const char *)arg_buffer_addr, (size_t)arg_buffer_size); At deallocation time the deallocate elements will be run in reverse order (again skipping any elements where fn is zero). The returned char * should be null to indicate success, or a non-null heap-allocated string error message to indicate failure. These actions allow finalization and deallocation to be extended to include operations like registering and deregistering eh-frames, TLS sections, initializer and deinitializers, and language metadata sections. Previously these operations required separate callWrapper invocations. Compared to callWrapper invocations, actions require no extra IPC/RPC, reducing costs and eliminating a potential source of errors. Finalize lifetime memory can be used to support finalize actions: Sections with finalize lifetime should be destroyed by memory managers immediately after finalization actions have been run. Finalize memory can be used to support finalize actions (e.g. with extra-metadata, or synthesized finalize actions) without incurring permanent memory overhead.
1 parent c30a528 commit e50aea5

34 files changed

+1692
-852
lines changed

llvm/examples/OrcV2Examples/LLJITWithCustomObjectLinkingLayer/LLJITWithCustomObjectLinkingLayer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ int main(int argc, char *argv[]) {
4747
.setObjectLinkingLayerCreator(
4848
[&](ExecutionSession &ES, const Triple &TT) {
4949
return std::make_unique<ObjectLinkingLayer>(
50-
ES, std::make_unique<jitlink::InProcessMemoryManager>());
50+
ES, ExitOnErr(jitlink::InProcessMemoryManager::Create()));
5151
})
5252
.create());
5353

llvm/examples/OrcV2Examples/LLJITWithObjectLinkingLayerPlugin/LLJITWithObjectLinkingLayerPlugin.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ int main(int argc, char *argv[]) {
209209
[&](ExecutionSession &ES, const Triple &TT) {
210210
// Create ObjectLinkingLayer.
211211
auto ObjLinkingLayer = std::make_unique<ObjectLinkingLayer>(
212-
ES, std::make_unique<jitlink::InProcessMemoryManager>());
212+
ES, ExitOnErr(jitlink::InProcessMemoryManager::Create()));
213213
// Add an instance of our plugin.
214214
ObjLinkingLayer->addPlugin(std::make_unique<MyPlugin>());
215215
return ObjLinkingLayer;

llvm/include/llvm/ExecutionEngine/JITLink/JITLink.h

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@
1313
#ifndef LLVM_EXECUTIONENGINE_JITLINK_JITLINK_H
1414
#define LLVM_EXECUTIONENGINE_JITLINK_JITLINK_H
1515

16-
#include "JITLinkMemoryManager.h"
1716
#include "llvm/ADT/DenseMap.h"
1817
#include "llvm/ADT/DenseSet.h"
1918
#include "llvm/ADT/Optional.h"
2019
#include "llvm/ADT/STLExtras.h"
2120
#include "llvm/ADT/Triple.h"
21+
#include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h"
22+
#include "llvm/ExecutionEngine/JITLink/MemoryFlags.h"
2223
#include "llvm/ExecutionEngine/JITSymbol.h"
2324
#include "llvm/Support/Allocator.h"
2425
#include "llvm/Support/Endian.h"
2526
#include "llvm/Support/Error.h"
2627
#include "llvm/Support/FormatVariadic.h"
2728
#include "llvm/Support/MathExtras.h"
28-
#include "llvm/Support/Memory.h"
2929
#include "llvm/Support/MemoryBuffer.h"
3030

3131
#include <map>
@@ -225,14 +225,15 @@ class Block : public Addressable {
225225

226226
/// Get the content for this block. Block must not be a zero-fill block.
227227
ArrayRef<char> getContent() const {
228-
assert(Data && "Section does not contain content");
228+
assert(Data && "Block does not contain content");
229229
return ArrayRef<char>(Data, Size);
230230
}
231231

232232
/// Set the content for this block.
233233
/// Caller is responsible for ensuring the underlying bytes are not
234234
/// deallocated while pointed to by this block.
235235
void setContent(ArrayRef<char> Content) {
236+
assert(Content.data() && "Setting null content");
236237
Data = Content.data();
237238
Size = Content.size();
238239
ContentMutable = false;
@@ -251,6 +252,7 @@ class Block : public Addressable {
251252
/// to call this on a block with immutable content -- consider using
252253
/// getMutableContent instead.
253254
MutableArrayRef<char> getAlreadyMutableContent() {
255+
assert(Data && "Block does not contain content");
254256
assert(ContentMutable && "Content is not mutable");
255257
return MutableArrayRef<char>(const_cast<char *>(Data), Size);
256258
}
@@ -260,6 +262,7 @@ class Block : public Addressable {
260262
/// The caller is responsible for ensuring that the memory pointed to by
261263
/// MutableContent is not deallocated while pointed to by this block.
262264
void setMutableContent(MutableArrayRef<char> MutableContent) {
265+
assert(MutableContent.data() && "Setting null content");
263266
Data = MutableContent.data();
264267
Size = MutableContent.size();
265268
ContentMutable = true;
@@ -295,6 +298,7 @@ class Block : public Addressable {
295298
/// Add an edge to this block.
296299
void addEdge(Edge::Kind K, Edge::OffsetT Offset, Symbol &Target,
297300
Edge::AddendT Addend) {
301+
assert(!isZeroFill() && "Adding edge to zero-fill block?");
298302
Edges.push_back(Edge(K, Offset, Target, Addend));
299303
}
300304

@@ -640,8 +644,7 @@ class Section {
640644
friend class LinkGraph;
641645

642646
private:
643-
Section(StringRef Name, sys::Memory::ProtectionFlags Prot,
644-
SectionOrdinal SecOrdinal)
647+
Section(StringRef Name, MemProt Prot, SectionOrdinal SecOrdinal)
645648
: Name(Name), Prot(Prot), SecOrdinal(SecOrdinal) {}
646649

647650
using SymbolSet = DenseSet<Symbol *>;
@@ -666,12 +669,16 @@ class Section {
666669
StringRef getName() const { return Name; }
667670

668671
/// Returns the protection flags for this section.
669-
sys::Memory::ProtectionFlags getProtectionFlags() const { return Prot; }
672+
MemProt getMemProt() const { return Prot; }
670673

671674
/// Set the protection flags for this section.
672-
void setProtectionFlags(sys::Memory::ProtectionFlags Prot) {
673-
this->Prot = Prot;
674-
}
675+
void setMemProt(MemProt Prot) { this->Prot = Prot; }
676+
677+
/// Get the deallocation policy for this section.
678+
MemDeallocPolicy getMemDeallocPolicy() const { return MDP; }
679+
680+
/// Set the deallocation policy for this section.
681+
void setMemDeallocPolicy(MemDeallocPolicy MDP) { this->MDP = MDP; }
675682

676683
/// Returns the ordinal for this section.
677684
SectionOrdinal getOrdinal() const { return SecOrdinal; }
@@ -686,6 +693,7 @@ class Section {
686693
return make_range(Blocks.begin(), Blocks.end());
687694
}
688695

696+
/// Returns the number of blocks in this section.
689697
BlockSet::size_type blocks_size() const { return Blocks.size(); }
690698

691699
/// Returns an iterator over the symbols defined in this section.
@@ -734,7 +742,8 @@ class Section {
734742
}
735743

736744
StringRef Name;
737-
sys::Memory::ProtectionFlags Prot;
745+
MemProt Prot;
746+
MemDeallocPolicy MDP = MemDeallocPolicy::Standard;
738747
SectionOrdinal SecOrdinal = 0;
739748
BlockSet Blocks;
740749
SymbolSet Symbols;
@@ -916,6 +925,11 @@ class LinkGraph {
916925
: Name(std::move(Name)), TT(TT), PointerSize(PointerSize),
917926
Endianness(Endianness), GetEdgeKindName(std::move(GetEdgeKindName)) {}
918927

928+
LinkGraph(const LinkGraph &) = delete;
929+
LinkGraph &operator=(const LinkGraph &) = delete;
930+
LinkGraph(LinkGraph &&) = delete;
931+
LinkGraph &operator=(LinkGraph &&) = delete;
932+
919933
/// Returns the name of this graph (usually the name of the original
920934
/// underlying MemoryBuffer).
921935
const std::string &getName() const { return Name; }
@@ -962,7 +976,7 @@ class LinkGraph {
962976
}
963977

964978
/// Create a section with the given name, protection flags, and alignment.
965-
Section &createSection(StringRef Name, sys::Memory::ProtectionFlags Prot) {
979+
Section &createSection(StringRef Name, MemProt Prot) {
966980
assert(llvm::find_if(Sections,
967981
[&](std::unique_ptr<Section> &Sec) {
968982
return Sec->getName() == Name;
@@ -1350,6 +1364,13 @@ class LinkGraph {
13501364
Sections.erase(I);
13511365
}
13521366

1367+
/// Accessor for the AllocActions object for this graph. This can be used to
1368+
/// register allocation action calls prior to finalization.
1369+
///
1370+
/// Accessing this object after finalization will result in undefined
1371+
/// behavior.
1372+
JITLinkMemoryManager::AllocActions &allocActions() { return AAs; }
1373+
13531374
/// Dump the graph.
13541375
void dump(raw_ostream &OS);
13551376

@@ -1366,6 +1387,7 @@ class LinkGraph {
13661387
SectionList Sections;
13671388
ExternalSymbolSet ExternalSymbols;
13681389
ExternalSymbolSet AbsoluteSymbols;
1390+
JITLinkMemoryManager::AllocActions AAs;
13691391
};
13701392

13711393
inline MutableArrayRef<char> Block::getMutableContent(LinkGraph &G) {
@@ -1655,8 +1677,7 @@ class JITLinkContext {
16551677
/// finalized (i.e. emitted to memory and memory permissions set). If all of
16561678
/// this objects dependencies have also been finalized then the code is ready
16571679
/// to run.
1658-
virtual void
1659-
notifyFinalized(std::unique_ptr<JITLinkMemoryManager::Allocation> A) = 0;
1680+
virtual void notifyFinalized(JITLinkMemoryManager::FinalizedAlloc Alloc) = 0;
16601681

16611682
/// Called by JITLink prior to linking to determine whether default passes for
16621683
/// the target should be added. The default implementation returns true.

0 commit comments

Comments
 (0)