//===-- JITLinkMemoryManager.h - JITLink mem manager interface --*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // Contains the JITLinkMemoryManager interface. // //===----------------------------------------------------------------------===// #ifndef LLVM_EXECUTIONENGINE_JITLINK_JITLINKMEMORYMANAGER_H #define LLVM_EXECUTIONENGINE_JITLINK_JITLINKMEMORYMANAGER_H #include "llvm/ADT/FunctionExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ExecutionEngine/JITLink/JITLinkDylib.h" #include "llvm/ExecutionEngine/Orc/Shared/AllocationActions.h" #include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" #include "llvm/ExecutionEngine/Orc/Shared/MemoryFlags.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/Error.h" #include "llvm/Support/MSVCErrorWorkarounds.h" #include "llvm/Support/Memory.h" #include "llvm/Support/RecyclingAllocator.h" #include #include #include namespace llvm { namespace jitlink { class Block; class LinkGraph; class Section; /// Manages allocations of JIT memory. /// /// Instances of this class may be accessed concurrently from multiple threads /// and their implemetations should include any necessary synchronization. class JITLinkMemoryManager { public: /// Represents a finalized allocation. /// /// Finalized allocations must be passed to the /// JITLinkMemoryManager:deallocate method prior to being destroyed. /// /// The interpretation of the Address associated with the finalized allocation /// is up to the memory manager implementation. Common options are using the /// base address of the allocation, or the address of a memory management /// object that tracks the allocation. class FinalizedAlloc { friend class JITLinkMemoryManager; static constexpr auto InvalidAddr = ~uint64_t(0); public: FinalizedAlloc() = default; explicit FinalizedAlloc(orc::ExecutorAddr A) : A(A) { assert(A.getValue() != InvalidAddr && "Explicitly creating an invalid allocation?"); } FinalizedAlloc(const FinalizedAlloc &) = delete; FinalizedAlloc(FinalizedAlloc &&Other) : A(Other.A) { Other.A.setValue(InvalidAddr); } FinalizedAlloc &operator=(const FinalizedAlloc &) = delete; FinalizedAlloc &operator=(FinalizedAlloc &&Other) { assert(A.getValue() == InvalidAddr && "Cannot overwrite active finalized allocation"); std::swap(A, Other.A); return *this; } ~FinalizedAlloc() { assert(A.getValue() == InvalidAddr && "Finalized allocation was not deallocated"); } /// FinalizedAllocs convert to false for default-constructed, and /// true otherwise. Default-constructed allocs need not be deallocated. explicit operator bool() const { return A.getValue() != InvalidAddr; } /// Returns the address associated with this finalized allocation. /// The allocation is unmodified. orc::ExecutorAddr getAddress() const { return A; } /// Returns the address associated with this finalized allocation and /// resets this object to the default state. /// This should only be used by allocators when deallocating memory. orc::ExecutorAddr release() { orc::ExecutorAddr Tmp = A; A.setValue(InvalidAddr); return Tmp; } private: orc::ExecutorAddr A{InvalidAddr}; }; /// Represents an allocation which has not been finalized yet. /// /// InFlightAllocs manage both executor memory allocations and working /// memory allocations. /// /// On finalization, the InFlightAlloc should transfer the content of /// working memory into executor memory, apply memory protections, and /// run any finalization functions. /// /// Working memory should be kept alive at least until one of the following /// happens: (1) the InFlightAlloc instance is destroyed, (2) the /// InFlightAlloc is abandoned, (3) finalized target memory is destroyed. /// /// If abandon is called then working memory and executor memory should both /// be freed. class InFlightAlloc { public: using OnFinalizedFunction = unique_function)>; using OnAbandonedFunction = unique_function; virtual ~InFlightAlloc(); /// Called prior to finalization if the allocation should be abandoned. virtual void abandon(OnAbandonedFunction OnAbandoned) = 0; /// Called to transfer working memory to the target and apply finalization. virtual void finalize(OnFinalizedFunction OnFinalized) = 0; /// Synchronous convenience version of finalize. Expected finalize() { std::promise> FinalizeResultP; auto FinalizeResultF = FinalizeResultP.get_future(); finalize([&](Expected Result) { FinalizeResultP.set_value(std::move(Result)); }); return FinalizeResultF.get(); } }; /// Typedef for the argument to be passed to OnAllocatedFunction. using AllocResult = Expected>; /// Called when allocation has been completed. using OnAllocatedFunction = unique_function; /// Called when deallocation has completed. using OnDeallocatedFunction = unique_function; virtual ~JITLinkMemoryManager(); /// Start the allocation process. /// /// If the initial allocation is successful then the OnAllocated function will /// be called with a std::unique_ptr value. If the assocation /// is unsuccessful then the OnAllocated function will be called with an /// Error. virtual void allocate(const JITLinkDylib *JD, LinkGraph &G, OnAllocatedFunction OnAllocated) = 0; /// Convenience function for blocking allocation. AllocResult allocate(const JITLinkDylib *JD, LinkGraph &G) { std::promise>> AllocResultP; auto AllocResultF = AllocResultP.get_future(); allocate(JD, G, [&](AllocResult Alloc) { AllocResultP.set_value(std::move(Alloc)); }); return AllocResultF.get(); } /// Deallocate a list of allocation objects. /// /// Dealloc actions will be run in reverse order (from the end of the vector /// to the start). virtual void deallocate(std::vector Allocs, OnDeallocatedFunction OnDeallocated) = 0; /// Convenience function for deallocation of a single alloc. void deallocate(FinalizedAlloc Alloc, OnDeallocatedFunction OnDeallocated) { std::vector Allocs; Allocs.push_back(std::move(Alloc)); deallocate(std::move(Allocs), std::move(OnDeallocated)); } /// Convenience function for blocking deallocation. Error deallocate(std::vector Allocs) { std::promise DeallocResultP; auto DeallocResultF = DeallocResultP.get_future(); deallocate(std::move(Allocs), [&](Error Err) { DeallocResultP.set_value(std::move(Err)); }); return DeallocResultF.get(); } /// Convenience function for blocking deallocation of a single alloc. Error deallocate(FinalizedAlloc Alloc) { std::vector Allocs; Allocs.push_back(std::move(Alloc)); return deallocate(std::move(Allocs)); } }; /// BasicLayout simplifies the implementation of JITLinkMemoryManagers. /// /// BasicLayout groups Sections into Segments based on their memory protection /// and deallocation policies. JITLinkMemoryManagers can construct a BasicLayout /// from a Graph, and then assign working memory and addresses to each of the /// Segments. These addreses will be mapped back onto the Graph blocks in /// the apply method. class BasicLayout { public: /// The Alignment, ContentSize and ZeroFillSize of each segment will be /// pre-filled from the Graph. Clients must set the Addr and WorkingMem fields /// prior to calling apply. // // FIXME: The C++98 initializer is an attempt to work around compile failures // due to http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1397. // We should be able to switch this back to member initialization once that // issue is fixed. class Segment { friend class BasicLayout; public: Segment() : ContentSize(0), ZeroFillSize(0), Addr(0), WorkingMem(nullptr), NextWorkingMemOffset(0) {} Align Alignment; size_t ContentSize; uint64_t ZeroFillSize; orc::ExecutorAddr Addr; char *WorkingMem = nullptr; private: size_t NextWorkingMemOffset; std::vector ContentBlocks, ZeroFillBlocks; }; /// A convenience class that further groups segments based on memory /// deallocation policy. This allows clients to make two slab allocations: /// one for all standard segments, and one for all finalize segments. struct ContiguousPageBasedLayoutSizes { uint64_t StandardSegs = 0; uint64_t FinalizeSegs = 0; uint64_t total() const { return StandardSegs + FinalizeSegs; } }; private: using SegmentMap = orc::AllocGroupSmallMap; public: BasicLayout(LinkGraph &G); /// Return a reference to the graph this allocation was created from. LinkGraph &getGraph() { return G; } /// Returns the total number of required to allocate all segments (with each /// segment padded out to page size) for all standard segments, and all /// finalize segments. /// /// This is a convenience function for the common case where the segments will /// be allocated contiguously. /// /// This function will return an error if any segment has an alignment that /// is higher than a page. Expected getContiguousPageBasedLayoutSizes(uint64_t PageSize); /// Returns an iterator over the segments of the layout. iterator_range segments() { return {Segments.begin(), Segments.end()}; } /// Apply the layout to the graph. Error apply(); /// Returns a reference to the AllocActions in the graph. /// This convenience function saves callers from having to #include /// LinkGraph.h if all they need are allocation actions. orc::shared::AllocActions &graphAllocActions(); private: LinkGraph &G; SegmentMap Segments; }; /// A utility class for making simple allocations using JITLinkMemoryManager. /// /// SimpleSegementAlloc takes a mapping of AllocGroups to Segments and uses /// this to create a LinkGraph with one Section (containing one Block) per /// Segment. Clients can obtain a pointer to the working memory and executor /// address of that block using the Segment's AllocGroup. Once memory has been /// populated, clients can call finalize to finalize the memory. /// /// Note: Segments with MemLifetime::NoAlloc are not permitted, since they would /// not be useful, and their presence is likely to indicate a bug. class SimpleSegmentAlloc { public: /// Describes a segment to be allocated. struct Segment { Segment() = default; Segment(size_t ContentSize, Align ContentAlign) : ContentSize(ContentSize), ContentAlign(ContentAlign) {} size_t ContentSize = 0; Align ContentAlign; }; /// Describes the segment working memory and executor address. struct SegmentInfo { orc::ExecutorAddr Addr; MutableArrayRef WorkingMem; }; using SegmentMap = orc::AllocGroupSmallMap; using OnCreatedFunction = unique_function)>; using OnFinalizedFunction = JITLinkMemoryManager::InFlightAlloc::OnFinalizedFunction; static void Create(JITLinkMemoryManager &MemMgr, const JITLinkDylib *JD, SegmentMap Segments, OnCreatedFunction OnCreated); static Expected Create(JITLinkMemoryManager &MemMgr, const JITLinkDylib *JD, SegmentMap Segments); SimpleSegmentAlloc(SimpleSegmentAlloc &&); SimpleSegmentAlloc &operator=(SimpleSegmentAlloc &&); ~SimpleSegmentAlloc(); /// Returns the SegmentInfo for the given group. SegmentInfo getSegInfo(orc::AllocGroup AG); /// Finalize all groups (async version). void finalize(OnFinalizedFunction OnFinalized) { Alloc->finalize(std::move(OnFinalized)); } /// Finalize all groups. Expected finalize() { return Alloc->finalize(); } private: SimpleSegmentAlloc( std::unique_ptr G, orc::AllocGroupSmallMap ContentBlocks, std::unique_ptr Alloc); std::unique_ptr G; orc::AllocGroupSmallMap ContentBlocks; std::unique_ptr Alloc; }; /// A JITLinkMemoryManager that allocates in-process memory. class InProcessMemoryManager : public JITLinkMemoryManager { public: class IPInFlightAlloc; /// Attempts to auto-detect the host page size. static Expected> Create(); /// Create an instance using the given page size. InProcessMemoryManager(uint64_t PageSize) : PageSize(PageSize) {} void allocate(const JITLinkDylib *JD, LinkGraph &G, OnAllocatedFunction OnAllocated) override; // Use overloads from base class. using JITLinkMemoryManager::allocate; void deallocate(std::vector Alloc, OnDeallocatedFunction OnDeallocated) override; // Use overloads from base class. using JITLinkMemoryManager::deallocate; private: // FIXME: Use an in-place array instead of a vector for DeallocActions. // There shouldn't need to be a heap alloc for this. struct FinalizedAllocInfo { sys::MemoryBlock StandardSegments; std::vector DeallocActions; }; FinalizedAlloc createFinalizedAlloc( sys::MemoryBlock StandardSegments, std::vector DeallocActions); uint64_t PageSize; std::mutex FinalizedAllocsMutex; RecyclingAllocator FinalizedAllocInfos; }; } // end namespace jitlink } // end namespace llvm #endif // LLVM_EXECUTIONENGINE_JITLINK_JITLINKMEMORYMANAGER_H