LLVM 24.0.0git
MapperJITLinkMemoryManager.cpp
Go to the documentation of this file.
1//=== MapperJITLinkMemoryManager.cpp - Memory management with MemoryMapper ===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10
12#include "llvm/Support/Error.h"
14
15using namespace llvm::jitlink;
16
17namespace llvm {
18namespace orc {
19
22public:
24 ExecutorAddr AllocAddr,
25 std::vector<MemoryMapper::AllocInfo::SegInfo> Segs)
26 : Parent(Parent), G(G), AllocAddr(AllocAddr), Segs(std::move(Segs)) {}
27
28 void finalize(OnFinalizedFunction OnFinalize) override {
30 AI.MappingBase = AllocAddr;
31
32 std::swap(AI.Segments, Segs);
33 std::swap(AI.Actions, G.allocActions());
34
35 Parent.Mapper->initialize(AI, [OnFinalize = std::move(OnFinalize)](
37 if (!Result) {
38 OnFinalize(Result.takeError());
39 return;
40 }
41
42 OnFinalize(FinalizedAlloc(*Result));
43 });
44 }
45
46 void abandon(OnAbandonedFunction OnFinalize) override {
47 Parent.Mapper->deinitialize({AllocAddr}, std::move(OnFinalize));
48 }
49
50private:
52 LinkGraph &G;
53 ExecutorAddr AllocAddr;
54 std::vector<MemoryMapper::AllocInfo::SegInfo> Segs;
55};
56
58 size_t ReservationGranularity, std::unique_ptr<MemoryMapper> Mapper,
59 bool ColocatePerJITDylib)
60 : ReservationUnits(ReservationGranularity),
61 ColocatePerJITDylib(ColocatePerJITDylib), Mapper(std::move(Mapper)) {}
62
63// Lazily creates and stores Key's pool on first access. Pools are
64// heap-allocated behind unique_ptr so the returned reference survives map
65// rehashing, and AMAllocator (declared before the pools) outlives them. Also
66// registers for a destruction notification the first time a JITDylib is
67// seen, so its pool can be freed in notifyDestroying().
68MapperJITLinkMemoryManager::AvailableMemoryMap &
69MapperJITLinkMemoryManager::getAvailableMemoryLocked(const JITLinkDylib *Key) {
70 auto [It, Inserted] = Pools.try_emplace(Key);
71 if (Inserted && Key)
72 const_cast<JITLinkDylib *>(Key)->notifyOnDestruction(*this);
73 auto &Pool = It->second.AvailPool;
74 if (!Pool)
75 Pool = std::make_unique<AvailableMemoryMap>(AMAllocator);
76 return *Pool;
77}
78
80 std::lock_guard<std::mutex> Lock(Mutex);
81 Pools.erase(&JD);
82}
83
85 std::lock_guard<std::mutex> Lock(Mutex);
86 OnSlabGrow = [](const JITLinkDylib *JD, size_t RequestedSize) -> Error {
87 return createStringError(
88 "JITDylib '%s' slab exhausted (single-slab mode); enable slab "
89 "growth to allow further reservations (%zu bytes requested)",
90 JD ? JD->getName().c_str() : "<unnamed>", RequestedSize);
91 };
92}
93
95 std::lock_guard<std::mutex> Lock(Mutex);
96 OnSlabGrow = [](const JITLinkDylib *, size_t) -> Error {
97 return Error::success();
98 };
99}
100
102 std::lock_guard<std::mutex> Lock(Mutex);
103 OnSlabGrow = std::move(Policy);
104}
105
108 std::optional<size_t> ReservationGranularity) {
109 auto Mapper = InProcessMemoryMapper::Create();
110 if (!Mapper)
111 return Mapper.takeError();
112 return std::make_unique<MapperJITLinkMemoryManager>(
113 ReservationGranularity.value_or(
115 std::move(*Mapper),
116 /*ColocatePerJITDylib=*/true);
117}
118
120 OnAllocatedFunction OnAllocated) {
121 BasicLayout BL(G);
122
123 // find required address space
124 auto SegsSizes = BL.getContiguousPageBasedLayoutSizes(Mapper->getPageSize());
125 if (!SegsSizes) {
126 OnAllocated(SegsSizes.takeError());
127 return;
128 }
129
130 auto TotalSize = SegsSizes->total();
131
132 // Which pool to draw from: the JITDylib's own pool when colocating
133 // per-JITDylib, otherwise a single shared (nullptr) pool.
134 const JITLinkDylib *PoolKey = ColocatePerJITDylib ? JD : nullptr;
135
136 auto CompleteAllocation = [this, PoolKey, &G, BL = std::move(BL),
137 OnAllocated = std::move(OnAllocated)](
139 if (!Result) {
140 Mutex.unlock();
141 return OnAllocated(Result.takeError());
142 }
143
144 auto NextSegAddr = Result->Start;
145
146 std::vector<MemoryMapper::AllocInfo::SegInfo> SegInfos;
147
148 for (auto &KV : BL.segments()) {
149 auto &AG = KV.first;
150 auto &Seg = KV.second;
151
152 auto TotalSize = Seg.ContentSize + Seg.ZeroFillSize;
153
154 Seg.Addr = NextSegAddr;
155 Seg.WorkingMem = Mapper->prepare(G, NextSegAddr, TotalSize);
156
157 NextSegAddr += alignTo(TotalSize, Mapper->getPageSize());
158
160 SI.Offset = Seg.Addr - Result->Start;
161 SI.ContentSize = Seg.ContentSize;
162 SI.ZeroFillSize = Seg.ZeroFillSize;
163 SI.AG = AG;
164 SI.WorkingMem = Seg.WorkingMem;
165
166 SegInfos.push_back(SI);
167 }
168
169 UsedMemory.insert({Result->Start, NextSegAddr - Result->Start});
170 AllocPoolKey[Result->Start] = PoolKey;
171
172 if (NextSegAddr < Result->End) {
173 // Save the remaining memory for reuse by later allocations from the same
174 // pool.
175 getAvailableMemoryLocked(PoolKey).insert(NextSegAddr, Result->End - 1,
176 true);
177 }
178 Mutex.unlock();
179
180 if (auto Err = BL.apply()) {
181 OnAllocated(std::move(Err));
182 return;
183 }
184
185 OnAllocated(std::make_unique<InFlightAlloc>(*this, G, Result->Start,
186 std::move(SegInfos)));
187 };
188
189 Mutex.lock();
190
191 // find an already reserved range in this pool that is large enough
192 ExecutorAddrRange SelectedRange{};
193
194 AvailableMemoryMap &Avail = getAvailableMemoryLocked(PoolKey);
195 for (AvailableMemoryMap::iterator It = Avail.begin(); It != Avail.end();
196 It++) {
197 if (It.stop() - It.start() + 1 >= TotalSize) {
198 SelectedRange = ExecutorAddrRange(It.start(), It.stop() + 1);
199 It.erase();
200 break;
201 }
202 }
203
204 if (SelectedRange.empty()) { // no already reserved range was found
205 // A reservation is needed. If this pool already owns one, reserving
206 // another (possibly out-of-range) slab may place its objects out of range,
207 // so consult the growth policy first.
208 bool IsFirstReservation = !Pools[PoolKey].Reserved;
209 Pools[PoolKey].Reserved = true;
210 auto TotalAllocation = alignTo(TotalSize, ReservationUnits);
211 if (!IsFirstReservation) {
212 if (auto Err = OnSlabGrow(JD, TotalAllocation))
213 return CompleteAllocation(std::move(Err));
214 }
215 Mapper->reserve(TotalAllocation,
216 [this, PoolKey, IsFirstReservation,
217 CompleteAllocation = std::move(CompleteAllocation)](
219 // If this was the pool's first reservation attempt and it
220 // failed, undo the Reserved flag so a later attempt is
221 // treated as the first one again, rather than
222 // incorrectly triggering the growth policy for a pool
223 // that never got a slab.
224 if (!Result && IsFirstReservation)
225 Pools[PoolKey].Reserved = false;
226 CompleteAllocation(std::move(Result));
227 });
228 } else {
229 CompleteAllocation(SelectedRange);
230 }
231}
232
234 std::vector<FinalizedAlloc> Allocs, OnDeallocatedFunction OnDeallocated) {
235 std::vector<ExecutorAddr> Bases;
236 Bases.reserve(Allocs.size());
237 for (auto &FA : Allocs) {
238 ExecutorAddr Addr = FA.getAddress();
239 Bases.push_back(Addr);
240 }
241
242 Mapper->deinitialize(Bases, [this, Allocs = std::move(Allocs),
243 OnDeallocated = std::move(OnDeallocated)](
244 llvm::Error Err) mutable {
245 // TODO: How should we treat memory that we fail to deinitialize?
246 // We're currently bailing out and treating it as "burned" -- should we
247 // require that a failure to deinitialize still reset the memory so that
248 // we can reclaim it?
249 if (Err) {
250 for (auto &FA : Allocs)
251 FA.release();
252 OnDeallocated(std::move(Err));
253 return;
254 }
255
256 {
257 std::lock_guard<std::mutex> Lock(Mutex);
258
259 for (auto &FA : Allocs) {
260 ExecutorAddr Addr = FA.getAddress();
261 ExecutorAddrDiff Size = UsedMemory[Addr];
262
263 UsedMemory.erase(Addr);
264
265 // Return the range to the pool it was allocated from.
266 const JITLinkDylib *PoolKey = AllocPoolKey.lookup(Addr);
267 AllocPoolKey.erase(Addr);
268 getAvailableMemoryLocked(PoolKey).insert(Addr, Addr + Size - 1, true);
269
270 FA.release();
271 }
272 }
273
274 OnDeallocated(Error::success());
275 });
276}
277
278} // end namespace orc
279} // end namespace llvm
#define G(x, y, z)
Definition MD5.cpp:55
Provides a library for accessing information about this process and other processes on the operating ...
Lightweight error class with error context and mandatory checking.
Definition Error.h:159
static ErrorSuccess success()
Create a success value.
Definition Error.h:336
Tagged union holding either a T or a Error.
Definition Error.h:485
const_iterator begin() const
const_iterator end() const
Represents an address in the executor process.
static Expected< std::unique_ptr< InProcessMemoryMapper > > Create()
InFlightAlloc(MapperJITLinkMemoryManager &Parent, LinkGraph &G, ExecutorAddr AllocAddr, std::vector< MemoryMapper::AllocInfo::SegInfo > Segs)
void finalize(OnFinalizedFunction OnFinalize) override
Called to transfer working memory to the target and apply finalization.
void abandon(OnAbandonedFunction OnFinalize) override
Called prior to finalization if the allocation should be abandoned.
void deallocate(std::vector< FinalizedAlloc > Allocs, OnDeallocatedFunction OnDeallocated) override
Deallocate a list of allocation objects.
MapperJITLinkMemoryManager(size_t ReservationGranularity, std::unique_ptr< MemoryMapper > Mapper, bool ColocatePerJITDylib=false)
If ColocatePerJITDylib is true each JITDylib draws from its own pool of reservations,...
void allowSlabGrowth()
Allow each JITDylib to grow into additional reservations on demand (the default).
static constexpr size_t defaultSlabSize()
The default per-slab reservation size used by createColocatingInProcessMemoryManager: 1 GiB on 64-bit...
unique_function< Error( const jitlink::JITLinkDylib *JD, size_t RequestedSize)> SlabGrowthPolicy
Policy invoked before reserving an additional slab for a JITDylib (i.e.
void setSlabPolicy(SlabGrowthPolicy Policy)
Set a custom slab-growth policy. See SlabGrowthPolicy above.
void denySlabGrowth()
Restrict each JITDylib to a single reservation ("slab").
void notifyDestroying(jitlink::JITLinkDylib &JD) override
Called when a registered JITDylib is destroyed (see jitlink::JITLinkDylib::notifyOnDestruction).
void allocate(const jitlink::JITLinkDylib *JD, jitlink::LinkGraph &G, OnAllocatedFunction OnAllocated) override
Start the allocation process.
uint64_t ExecutorAddrDiff
LLVM_ABI Expected< std::unique_ptr< MapperJITLinkMemoryManager > > createColocatingInProcessMemoryManager(std::optional< size_t > ReservationGranularity=std::nullopt)
Create an in-process slab allocator that colocates each JITDylib's objects within its own reservation...
This is an optimization pass for GlobalISel generic memory operations.
Error createStringError(std::error_code EC, char const *Fmt, const Ts &... Vals)
Create formatted StringError object.
Definition Error.h:1321
constexpr uint64_t alignTo(uint64_t Size, Align A)
Returns a multiple of A needed to store Size bytes.
Definition Alignment.h:149
LLVM_ATTRIBUTE_VISIBILITY_DEFAULT AnalysisKey InnerAnalysisManagerProxy< AnalysisManagerT, IRUnitT, ExtraArgTs... >::Key
OutputIt move(R &&Range, OutputIt Out)
Provide wrappers to std::move which take ranges instead of having to pass begin/end explicitly.
Definition STLExtras.h:1933
@ End
Run the machine verifier once, at the end of the pipeline.
Definition CodeGen.h:320
Implement std::hash so that hash_code can be used in STL containers.
Definition BitVector.h:879
void swap(llvm::BitVector &LHS, llvm::BitVector &RHS)
Implement std::swap in terms of BitVector swap.
Definition BitVector.h:881
Represents an address range in the exceutor process.
Represents a single allocation containing multiple segments and initialization and deinitialization a...
std::vector< SegInfo > Segments