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 |