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