Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
14 | pmbaty | 1 | //===-- JITLinkMemoryManager.h - JITLink mem manager interface --*- C++ -*-===// |
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 | // |
||
9 | // Contains the JITLinkMemoryManager interface. |
||
10 | // |
||
11 | //===----------------------------------------------------------------------===// |
||
12 | |||
13 | #ifndef LLVM_EXECUTIONENGINE_JITLINK_JITLINKMEMORYMANAGER_H |
||
14 | #define LLVM_EXECUTIONENGINE_JITLINK_JITLINKMEMORYMANAGER_H |
||
15 | |||
16 | #include "llvm/ADT/FunctionExtras.h" |
||
17 | #include "llvm/ADT/SmallVector.h" |
||
18 | #include "llvm/ExecutionEngine/JITLink/JITLinkDylib.h" |
||
19 | #include "llvm/ExecutionEngine/Orc/Shared/AllocationActions.h" |
||
20 | #include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" |
||
21 | #include "llvm/ExecutionEngine/Orc/Shared/MemoryFlags.h" |
||
22 | #include "llvm/Support/Allocator.h" |
||
23 | #include "llvm/Support/Error.h" |
||
24 | #include "llvm/Support/MSVCErrorWorkarounds.h" |
||
25 | #include "llvm/Support/Memory.h" |
||
26 | #include "llvm/Support/RecyclingAllocator.h" |
||
27 | |||
28 | #include <cstdint> |
||
29 | #include <future> |
||
30 | #include <mutex> |
||
31 | |||
32 | namespace llvm { |
||
33 | namespace jitlink { |
||
34 | |||
35 | class Block; |
||
36 | class LinkGraph; |
||
37 | class Section; |
||
38 | |||
39 | /// Manages allocations of JIT memory. |
||
40 | /// |
||
41 | /// Instances of this class may be accessed concurrently from multiple threads |
||
42 | /// and their implemetations should include any necessary synchronization. |
||
43 | class JITLinkMemoryManager { |
||
44 | public: |
||
45 | |||
46 | /// Represents a finalized allocation. |
||
47 | /// |
||
48 | /// Finalized allocations must be passed to the |
||
49 | /// JITLinkMemoryManager:deallocate method prior to being destroyed. |
||
50 | /// |
||
51 | /// The interpretation of the Address associated with the finalized allocation |
||
52 | /// is up to the memory manager implementation. Common options are using the |
||
53 | /// base address of the allocation, or the address of a memory management |
||
54 | /// object that tracks the allocation. |
||
55 | class FinalizedAlloc { |
||
56 | friend class JITLinkMemoryManager; |
||
57 | |||
58 | static constexpr auto InvalidAddr = ~uint64_t(0); |
||
59 | |||
60 | public: |
||
61 | FinalizedAlloc() = default; |
||
62 | explicit FinalizedAlloc(orc::ExecutorAddr A) : A(A) { |
||
63 | assert(A.getValue() != InvalidAddr && |
||
64 | "Explicitly creating an invalid allocation?"); |
||
65 | } |
||
66 | FinalizedAlloc(const FinalizedAlloc &) = delete; |
||
67 | FinalizedAlloc(FinalizedAlloc &&Other) : A(Other.A) { |
||
68 | Other.A.setValue(InvalidAddr); |
||
69 | } |
||
70 | FinalizedAlloc &operator=(const FinalizedAlloc &) = delete; |
||
71 | FinalizedAlloc &operator=(FinalizedAlloc &&Other) { |
||
72 | assert(A.getValue() == InvalidAddr && |
||
73 | "Cannot overwrite active finalized allocation"); |
||
74 | std::swap(A, Other.A); |
||
75 | return *this; |
||
76 | } |
||
77 | ~FinalizedAlloc() { |
||
78 | assert(A.getValue() == InvalidAddr && |
||
79 | "Finalized allocation was not deallocated"); |
||
80 | } |
||
81 | |||
82 | /// FinalizedAllocs convert to false for default-constructed, and |
||
83 | /// true otherwise. Default-constructed allocs need not be deallocated. |
||
84 | explicit operator bool() const { return A.getValue() != InvalidAddr; } |
||
85 | |||
86 | /// Returns the address associated with this finalized allocation. |
||
87 | /// The allocation is unmodified. |
||
88 | orc::ExecutorAddr getAddress() const { return A; } |
||
89 | |||
90 | /// Returns the address associated with this finalized allocation and |
||
91 | /// resets this object to the default state. |
||
92 | /// This should only be used by allocators when deallocating memory. |
||
93 | orc::ExecutorAddr release() { |
||
94 | orc::ExecutorAddr Tmp = A; |
||
95 | A.setValue(InvalidAddr); |
||
96 | return Tmp; |
||
97 | } |
||
98 | |||
99 | private: |
||
100 | orc::ExecutorAddr A{InvalidAddr}; |
||
101 | }; |
||
102 | |||
103 | /// Represents an allocation which has not been finalized yet. |
||
104 | /// |
||
105 | /// InFlightAllocs manage both executor memory allocations and working |
||
106 | /// memory allocations. |
||
107 | /// |
||
108 | /// On finalization, the InFlightAlloc should transfer the content of |
||
109 | /// working memory into executor memory, apply memory protections, and |
||
110 | /// run any finalization functions. |
||
111 | /// |
||
112 | /// Working memory should be kept alive at least until one of the following |
||
113 | /// happens: (1) the InFlightAlloc instance is destroyed, (2) the |
||
114 | /// InFlightAlloc is abandoned, (3) finalized target memory is destroyed. |
||
115 | /// |
||
116 | /// If abandon is called then working memory and executor memory should both |
||
117 | /// be freed. |
||
118 | class InFlightAlloc { |
||
119 | public: |
||
120 | using OnFinalizedFunction = unique_function<void(Expected<FinalizedAlloc>)>; |
||
121 | using OnAbandonedFunction = unique_function<void(Error)>; |
||
122 | |||
123 | virtual ~InFlightAlloc(); |
||
124 | |||
125 | /// Called prior to finalization if the allocation should be abandoned. |
||
126 | virtual void abandon(OnAbandonedFunction OnAbandoned) = 0; |
||
127 | |||
128 | /// Called to transfer working memory to the target and apply finalization. |
||
129 | virtual void finalize(OnFinalizedFunction OnFinalized) = 0; |
||
130 | |||
131 | /// Synchronous convenience version of finalize. |
||
132 | Expected<FinalizedAlloc> finalize() { |
||
133 | std::promise<MSVCPExpected<FinalizedAlloc>> FinalizeResultP; |
||
134 | auto FinalizeResultF = FinalizeResultP.get_future(); |
||
135 | finalize([&](Expected<FinalizedAlloc> Result) { |
||
136 | FinalizeResultP.set_value(std::move(Result)); |
||
137 | }); |
||
138 | return FinalizeResultF.get(); |
||
139 | } |
||
140 | }; |
||
141 | |||
142 | /// Typedef for the argument to be passed to OnAllocatedFunction. |
||
143 | using AllocResult = Expected<std::unique_ptr<InFlightAlloc>>; |
||
144 | |||
145 | /// Called when allocation has been completed. |
||
146 | using OnAllocatedFunction = unique_function<void(AllocResult)>; |
||
147 | |||
148 | /// Called when deallocation has completed. |
||
149 | using OnDeallocatedFunction = unique_function<void(Error)>; |
||
150 | |||
151 | virtual ~JITLinkMemoryManager(); |
||
152 | |||
153 | /// Start the allocation process. |
||
154 | /// |
||
155 | /// If the initial allocation is successful then the OnAllocated function will |
||
156 | /// be called with a std::unique_ptr<InFlightAlloc> value. If the assocation |
||
157 | /// is unsuccessful then the OnAllocated function will be called with an |
||
158 | /// Error. |
||
159 | virtual void allocate(const JITLinkDylib *JD, LinkGraph &G, |
||
160 | OnAllocatedFunction OnAllocated) = 0; |
||
161 | |||
162 | /// Convenience function for blocking allocation. |
||
163 | AllocResult allocate(const JITLinkDylib *JD, LinkGraph &G) { |
||
164 | std::promise<MSVCPExpected<std::unique_ptr<InFlightAlloc>>> AllocResultP; |
||
165 | auto AllocResultF = AllocResultP.get_future(); |
||
166 | allocate(JD, G, [&](AllocResult Alloc) { |
||
167 | AllocResultP.set_value(std::move(Alloc)); |
||
168 | }); |
||
169 | return AllocResultF.get(); |
||
170 | } |
||
171 | |||
172 | /// Deallocate a list of allocation objects. |
||
173 | /// |
||
174 | /// Dealloc actions will be run in reverse order (from the end of the vector |
||
175 | /// to the start). |
||
176 | virtual void deallocate(std::vector<FinalizedAlloc> Allocs, |
||
177 | OnDeallocatedFunction OnDeallocated) = 0; |
||
178 | |||
179 | /// Convenience function for deallocation of a single alloc. |
||
180 | void deallocate(FinalizedAlloc Alloc, OnDeallocatedFunction OnDeallocated) { |
||
181 | std::vector<FinalizedAlloc> Allocs; |
||
182 | Allocs.push_back(std::move(Alloc)); |
||
183 | deallocate(std::move(Allocs), std::move(OnDeallocated)); |
||
184 | } |
||
185 | |||
186 | /// Convenience function for blocking deallocation. |
||
187 | Error deallocate(std::vector<FinalizedAlloc> Allocs) { |
||
188 | std::promise<MSVCPError> DeallocResultP; |
||
189 | auto DeallocResultF = DeallocResultP.get_future(); |
||
190 | deallocate(std::move(Allocs), |
||
191 | [&](Error Err) { DeallocResultP.set_value(std::move(Err)); }); |
||
192 | return DeallocResultF.get(); |
||
193 | } |
||
194 | |||
195 | /// Convenience function for blocking deallocation of a single alloc. |
||
196 | Error deallocate(FinalizedAlloc Alloc) { |
||
197 | std::vector<FinalizedAlloc> Allocs; |
||
198 | Allocs.push_back(std::move(Alloc)); |
||
199 | return deallocate(std::move(Allocs)); |
||
200 | } |
||
201 | }; |
||
202 | |||
203 | /// BasicLayout simplifies the implementation of JITLinkMemoryManagers. |
||
204 | /// |
||
205 | /// BasicLayout groups Sections into Segments based on their memory protection |
||
206 | /// and deallocation policies. JITLinkMemoryManagers can construct a BasicLayout |
||
207 | /// from a Graph, and then assign working memory and addresses to each of the |
||
208 | /// Segments. These addreses will be mapped back onto the Graph blocks in |
||
209 | /// the apply method. |
||
210 | class BasicLayout { |
||
211 | public: |
||
212 | /// The Alignment, ContentSize and ZeroFillSize of each segment will be |
||
213 | /// pre-filled from the Graph. Clients must set the Addr and WorkingMem fields |
||
214 | /// prior to calling apply. |
||
215 | // |
||
216 | // FIXME: The C++98 initializer is an attempt to work around compile failures |
||
217 | // due to http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1397. |
||
218 | // We should be able to switch this back to member initialization once that |
||
219 | // issue is fixed. |
||
220 | class Segment { |
||
221 | friend class BasicLayout; |
||
222 | |||
223 | public: |
||
224 | Segment() |
||
225 | : ContentSize(0), ZeroFillSize(0), Addr(0), WorkingMem(nullptr), |
||
226 | NextWorkingMemOffset(0) {} |
||
227 | Align Alignment; |
||
228 | size_t ContentSize; |
||
229 | uint64_t ZeroFillSize; |
||
230 | orc::ExecutorAddr Addr; |
||
231 | char *WorkingMem = nullptr; |
||
232 | |||
233 | private: |
||
234 | size_t NextWorkingMemOffset; |
||
235 | std::vector<Block *> ContentBlocks, ZeroFillBlocks; |
||
236 | }; |
||
237 | |||
238 | /// A convenience class that further groups segments based on memory |
||
239 | /// deallocation policy. This allows clients to make two slab allocations: |
||
240 | /// one for all standard segments, and one for all finalize segments. |
||
241 | struct ContiguousPageBasedLayoutSizes { |
||
242 | uint64_t StandardSegs = 0; |
||
243 | uint64_t FinalizeSegs = 0; |
||
244 | |||
245 | uint64_t total() const { return StandardSegs + FinalizeSegs; } |
||
246 | }; |
||
247 | |||
248 | private: |
||
249 | using SegmentMap = orc::AllocGroupSmallMap<Segment>; |
||
250 | |||
251 | public: |
||
252 | BasicLayout(LinkGraph &G); |
||
253 | |||
254 | /// Return a reference to the graph this allocation was created from. |
||
255 | LinkGraph &getGraph() { return G; } |
||
256 | |||
257 | /// Returns the total number of required to allocate all segments (with each |
||
258 | /// segment padded out to page size) for all standard segments, and all |
||
259 | /// finalize segments. |
||
260 | /// |
||
261 | /// This is a convenience function for the common case where the segments will |
||
262 | /// be allocated contiguously. |
||
263 | /// |
||
264 | /// This function will return an error if any segment has an alignment that |
||
265 | /// is higher than a page. |
||
266 | Expected<ContiguousPageBasedLayoutSizes> |
||
267 | getContiguousPageBasedLayoutSizes(uint64_t PageSize); |
||
268 | |||
269 | /// Returns an iterator over the segments of the layout. |
||
270 | iterator_range<SegmentMap::iterator> segments() { |
||
271 | return {Segments.begin(), Segments.end()}; |
||
272 | } |
||
273 | |||
274 | /// Apply the layout to the graph. |
||
275 | Error apply(); |
||
276 | |||
277 | /// Returns a reference to the AllocActions in the graph. |
||
278 | /// This convenience function saves callers from having to #include |
||
279 | /// LinkGraph.h if all they need are allocation actions. |
||
280 | orc::shared::AllocActions &graphAllocActions(); |
||
281 | |||
282 | private: |
||
283 | LinkGraph &G; |
||
284 | SegmentMap Segments; |
||
285 | }; |
||
286 | |||
287 | /// A utility class for making simple allocations using JITLinkMemoryManager. |
||
288 | /// |
||
289 | /// SimpleSegementAlloc takes a mapping of AllocGroups to Segments and uses |
||
290 | /// this to create a LinkGraph with one Section (containing one Block) per |
||
291 | /// Segment. Clients can obtain a pointer to the working memory and executor |
||
292 | /// address of that block using the Segment's AllocGroup. Once memory has been |
||
293 | /// populated, clients can call finalize to finalize the memory. |
||
294 | class SimpleSegmentAlloc { |
||
295 | public: |
||
296 | /// Describes a segment to be allocated. |
||
297 | struct Segment { |
||
298 | Segment() = default; |
||
299 | Segment(size_t ContentSize, Align ContentAlign) |
||
300 | : ContentSize(ContentSize), ContentAlign(ContentAlign) {} |
||
301 | |||
302 | size_t ContentSize = 0; |
||
303 | Align ContentAlign; |
||
304 | }; |
||
305 | |||
306 | /// Describes the segment working memory and executor address. |
||
307 | struct SegmentInfo { |
||
308 | orc::ExecutorAddr Addr; |
||
309 | MutableArrayRef<char> WorkingMem; |
||
310 | }; |
||
311 | |||
312 | using SegmentMap = orc::AllocGroupSmallMap<Segment>; |
||
313 | |||
314 | using OnCreatedFunction = unique_function<void(Expected<SimpleSegmentAlloc>)>; |
||
315 | |||
316 | using OnFinalizedFunction = |
||
317 | JITLinkMemoryManager::InFlightAlloc::OnFinalizedFunction; |
||
318 | |||
319 | static void Create(JITLinkMemoryManager &MemMgr, const JITLinkDylib *JD, |
||
320 | SegmentMap Segments, OnCreatedFunction OnCreated); |
||
321 | |||
322 | static Expected<SimpleSegmentAlloc> Create(JITLinkMemoryManager &MemMgr, |
||
323 | const JITLinkDylib *JD, |
||
324 | SegmentMap Segments); |
||
325 | |||
326 | SimpleSegmentAlloc(SimpleSegmentAlloc &&); |
||
327 | SimpleSegmentAlloc &operator=(SimpleSegmentAlloc &&); |
||
328 | ~SimpleSegmentAlloc(); |
||
329 | |||
330 | /// Returns the SegmentInfo for the given group. |
||
331 | SegmentInfo getSegInfo(orc::AllocGroup AG); |
||
332 | |||
333 | /// Finalize all groups (async version). |
||
334 | void finalize(OnFinalizedFunction OnFinalized) { |
||
335 | Alloc->finalize(std::move(OnFinalized)); |
||
336 | } |
||
337 | |||
338 | /// Finalize all groups. |
||
339 | Expected<JITLinkMemoryManager::FinalizedAlloc> finalize() { |
||
340 | return Alloc->finalize(); |
||
341 | } |
||
342 | |||
343 | private: |
||
344 | SimpleSegmentAlloc( |
||
345 | std::unique_ptr<LinkGraph> G, |
||
346 | orc::AllocGroupSmallMap<Block *> ContentBlocks, |
||
347 | std::unique_ptr<JITLinkMemoryManager::InFlightAlloc> Alloc); |
||
348 | |||
349 | std::unique_ptr<LinkGraph> G; |
||
350 | orc::AllocGroupSmallMap<Block *> ContentBlocks; |
||
351 | std::unique_ptr<JITLinkMemoryManager::InFlightAlloc> Alloc; |
||
352 | }; |
||
353 | |||
354 | /// A JITLinkMemoryManager that allocates in-process memory. |
||
355 | class InProcessMemoryManager : public JITLinkMemoryManager { |
||
356 | public: |
||
357 | class IPInFlightAlloc; |
||
358 | |||
359 | /// Attempts to auto-detect the host page size. |
||
360 | static Expected<std::unique_ptr<InProcessMemoryManager>> Create(); |
||
361 | |||
362 | /// Create an instance using the given page size. |
||
363 | InProcessMemoryManager(uint64_t PageSize) : PageSize(PageSize) {} |
||
364 | |||
365 | void allocate(const JITLinkDylib *JD, LinkGraph &G, |
||
366 | OnAllocatedFunction OnAllocated) override; |
||
367 | |||
368 | // Use overloads from base class. |
||
369 | using JITLinkMemoryManager::allocate; |
||
370 | |||
371 | void deallocate(std::vector<FinalizedAlloc> Alloc, |
||
372 | OnDeallocatedFunction OnDeallocated) override; |
||
373 | |||
374 | // Use overloads from base class. |
||
375 | using JITLinkMemoryManager::deallocate; |
||
376 | |||
377 | private: |
||
378 | // FIXME: Use an in-place array instead of a vector for DeallocActions. |
||
379 | // There shouldn't need to be a heap alloc for this. |
||
380 | struct FinalizedAllocInfo { |
||
381 | sys::MemoryBlock StandardSegments; |
||
382 | std::vector<orc::shared::WrapperFunctionCall> DeallocActions; |
||
383 | }; |
||
384 | |||
385 | FinalizedAlloc createFinalizedAlloc( |
||
386 | sys::MemoryBlock StandardSegments, |
||
387 | std::vector<orc::shared::WrapperFunctionCall> DeallocActions); |
||
388 | |||
389 | uint64_t PageSize; |
||
390 | std::mutex FinalizedAllocsMutex; |
||
391 | RecyclingAllocator<BumpPtrAllocator, FinalizedAllocInfo> FinalizedAllocInfos; |
||
392 | }; |
||
393 | |||
394 | } // end namespace jitlink |
||
395 | } // end namespace llvm |
||
396 | |||
397 | #endif // LLVM_EXECUTIONENGINE_JITLINK_JITLINKMEMORYMANAGER_H |