Line data Source code
1 : #ifdef __PLUMED_HAS_ASMJIT 2 : // This file is part of AsmJit project <https://asmjit.com> 3 : // 4 : // See asmjit.h or LICENSE.md for license and copyright information 5 : // SPDX-License-Identifier: Zlib 6 : 7 : #ifndef ASMJIT_CORE_JITALLOCATOR_H_INCLUDED 8 : #define ASMJIT_CORE_JITALLOCATOR_H_INCLUDED 9 : 10 : #include "api-config.h" 11 : #ifndef ASMJIT_NO_JIT 12 : 13 : #include "globals.h" 14 : #include "virtmem.h" 15 : 16 : ASMJIT_BEGIN_NAMESPACE 17 : 18 : //! \addtogroup asmjit_virtual_memory 19 : //! \{ 20 : 21 : //! Options used by \ref JitAllocator. 22 : enum class JitAllocatorOptions : uint32_t { 23 : //! No options. 24 : kNone = 0, 25 : 26 : //! Enables the use of an anonymous memory-mapped memory that is mapped into two buffers having a different pointer. 27 : //! The first buffer has read and execute permissions and the second buffer has read+write permissions. 28 : //! 29 : //! See \ref VirtMem::allocDualMapping() for more details about this feature. 30 : kUseDualMapping = 0x00000001u, 31 : 32 : //! Enables the use of multiple pools with increasing granularity instead of a single pool. This flag would enable 33 : //! 3 internal pools in total having 64, 128, and 256 bytes granularity. 34 : //! 35 : //! This feature is only recommended for users that generate a lot of code and would like to minimize the overhead 36 : //! of `JitAllocator` itself by having blocks of different allocation granularities. Using this feature only for 37 : //! few allocations won't pay off as the allocator may need to create more blocks initially before it can take the 38 : //! advantage of variable block granularity. 39 : kUseMultiplePools = 0x00000002u, 40 : 41 : //! Always fill reserved memory by a fill-pattern. 42 : //! 43 : //! Causes a new block to be cleared by the fill pattern and freshly released memory to be cleared before making 44 : //! it ready for another use. 45 : kFillUnusedMemory = 0x00000004u, 46 : 47 : //! When this flag is set the allocator would immediately release unused blocks during `release()` or `reset()`. 48 : //! When this flag is not set the allocator would keep one empty block in each pool to prevent excessive virtual 49 : //! memory allocations and deallocations in border cases, which involve constantly allocating and deallocating a 50 : //! single block caused by repetitive calling `alloc()` and `release()` when the allocator has either no blocks 51 : //! or have all blocks fully occupied. 52 : kImmediateRelease = 0x00000008u, 53 : 54 : //! Use a custom fill pattern, must be combined with `kFlagFillUnusedMemory`. 55 : kCustomFillPattern = 0x10000000u 56 : }; 57 27422 : ASMJIT_DEFINE_ENUM_FLAGS(JitAllocatorOptions) 58 : 59 : //! A simple implementation of memory manager that uses `asmjit::VirtMem` 60 : //! functions to manage virtual memory for JIT compiled code. 61 : //! 62 : //! Implementation notes: 63 : //! 64 : //! - Granularity of allocated blocks is different than granularity for a typical C malloc. In addition, the allocator 65 : //! can use several memory pools having a different granularity to minimize the maintenance overhead. Multiple pools 66 : //! feature requires `kFlagUseMultiplePools` flag to be set. 67 : //! 68 : //! - The allocator doesn't store any information in executable memory, instead, the implementation uses two 69 : //! bit-vectors to manage allocated memory of each allocator-block. The first bit-vector called 'used' is used to 70 : //! track used memory (where each bit represents memory size defined by granularity) and the second bit vector called 71 : //! 'stop' is used as a sentinel to mark where the allocated area ends. 72 : //! 73 : //! - Internally, the allocator also uses RB tree to keep track of all blocks across all pools. Each inserted block is 74 : //! added to the tree so it can be matched fast during `release()` and `shrink()`. 75 : class JitAllocator { 76 : public: 77 : ASMJIT_NONCOPYABLE(JitAllocator) 78 : 79 : struct Impl { 80 : //! Allocator options. 81 : JitAllocatorOptions options; 82 : //! Base block size (0 if the allocator is not initialized). 83 : uint32_t blockSize; 84 : //! Base granularity (0 if the allocator is not initialized). 85 : uint32_t granularity; 86 : //! A pattern that is used to fill unused memory if secure mode is enabled. 87 : uint32_t fillPattern; 88 : }; 89 : 90 : //! Allocator implementation (private). 91 : Impl* _impl; 92 : 93 : //! \name Construction & Destruction 94 : //! \{ 95 : 96 : //! Parameters that can be passed to `JitAllocator` constructor. 97 : //! 98 : //! Use it like this: 99 : //! 100 : //! ``` 101 : //! // Zero initialize (zero means the default value) and change what you need. 102 : //! JitAllocator::CreateParams params {}; 103 : //! params.blockSize = 1024 * 1024; 104 : //! 105 : //! // Create the allocator. 106 : //! JitAllocator allocator(¶ms); 107 : //! ``` 108 : struct CreateParams { 109 : //! Allocator options. 110 : //! 111 : //! No options are used by default. 112 : JitAllocatorOptions options = JitAllocatorOptions::kNone; 113 : 114 : //! Base size of a single block in bytes (default 64kB). 115 : //! 116 : //! \remarks Block size must be equal to or greater than page size and must be power of 2. If the input is not 117 : //! valid then the default block size will be used instead. 118 : uint32_t blockSize = 0; 119 : 120 : //! Base granularity (and also natural alignment) of allocations in bytes (default 64). 121 : //! 122 : //! Since the `JitAllocator` uses bit-arrays to mark used memory the granularity also specifies how many bytes 123 : //! correspond to a single bit in such bit-array. Higher granularity means more waste of virtual memory (as it 124 : //! increases the natural alignment), but smaller bit-arrays as less bits would be required per a single block. 125 : uint32_t granularity = 0; 126 : 127 : //! Patter to use to fill unused memory. 128 : //! 129 : //! Only used if \ref JitAllocatorOptions::kCustomFillPattern is set. 130 : uint32_t fillPattern = 0; 131 : 132 : // Reset the content of `CreateParams`. 133 : inline void reset() noexcept { memset(this, 0, sizeof(*this)); } 134 : }; 135 : 136 : //! Creates a `JitAllocator` instance. 137 : ASMJIT_API explicit JitAllocator(const CreateParams* params = nullptr) noexcept; 138 : //! Destroys the `JitAllocator` instance and release all blocks held. 139 : ASMJIT_API ~JitAllocator() noexcept; 140 : 141 : inline bool isInitialized() const noexcept { return _impl->blockSize == 0; } 142 : 143 : //! Free all allocated memory - makes all pointers returned by `alloc()` invalid. 144 : //! 145 : //! \remarks This function is not thread-safe as it's designed to be used when nobody else is using allocator. 146 : //! The reason is that there is no point of calling `reset()` when the allocator is still in use. 147 : ASMJIT_API void reset(ResetPolicy resetPolicy = ResetPolicy::kSoft) noexcept; 148 : 149 : //! \} 150 : 151 : //! \name Accessors 152 : //! \{ 153 : 154 : //! Returns allocator options, see `Flags`. 155 : inline JitAllocatorOptions options() const noexcept { return _impl->options; } 156 : //! Tests whether the allocator has the given `option` set. 157 : inline bool hasOption(JitAllocatorOptions option) const noexcept { return uint32_t(_impl->options & option) != 0; } 158 : 159 : //! Returns a base block size (a minimum size of block that the allocator would allocate). 160 : inline uint32_t blockSize() const noexcept { return _impl->blockSize; } 161 : //! Returns granularity of the allocator. 162 : inline uint32_t granularity() const noexcept { return _impl->granularity; } 163 : //! Returns pattern that is used to fill unused memory if `kFlagUseFillPattern` is set. 164 0 : inline uint32_t fillPattern() const noexcept { return _impl->fillPattern; } 165 : 166 : //! \} 167 : 168 : //! \name Alloc & Release 169 : //! \{ 170 : 171 : //! Allocates a new memory block of the requested `size`. 172 : //! 173 : //! When the function is successful it stores two pointers in `rxPtrOut` and `rwPtrOut`. The pointers will be 174 : //! different only if `kOptionUseDualMapping` was used to setup the allocator (in that case the `rxPtrOut` would 175 : //! point to a Read+Execute region and `rwPtrOut` would point to a Read+Write region of the same memory-mapped block. 176 : ASMJIT_API Error alloc(void** rxPtrOut, void** rwPtrOut, size_t size) noexcept; 177 : 178 : //! Releases a memory block returned by `alloc()`. 179 : //! 180 : //! \remarks This function is thread-safe. 181 : ASMJIT_API Error release(void* rxPtr) noexcept; 182 : 183 : //! Frees extra memory allocated with `rxPtr` by shrinking it to the given `newSize`. 184 : //! 185 : //! \remarks This function is thread-safe. 186 : ASMJIT_API Error shrink(void* rxPtr, size_t newSize) noexcept; 187 : 188 : //! Queries information about an allocated memory block that contains the given `rxPtr`. 189 : //! 190 : //! The function returns `kErrorOk` when `rxPtr` is matched and fills `rxPtrOut`, `rwPtrOut`, and `sizeOut` output 191 : //! arguments. The returned `rxPtrOut` and `rwPtrOut` pointers point to the beginning of the block, and `sizeOut` 192 : //! describes the total amount of bytes this allocation uses - `sizeOut` will always be aligned to the allocation 193 : //! granularity, so for example if an allocation was 1 byte and the size granularity is 64, the returned `sizeOut` 194 : //! will be 64 bytes, because that's what the allocator sees. 195 : ASMJIT_API Error query(void* rxPtr, void** rxPtrOut, void** rwPtrOut, size_t* sizeOut) const noexcept; 196 : 197 : //! \} 198 : 199 : //! \name Statistics 200 : //! \{ 201 : 202 : //! Statistics about `JitAllocator`. 203 : struct Statistics { 204 : //! Number of blocks `JitAllocator` maintains. 205 : size_t _blockCount; 206 : //! Number of active allocations. 207 : size_t _allocationCount; 208 : //! How many bytes are currently used / allocated. 209 : size_t _usedSize; 210 : //! How many bytes are currently reserved by the allocator. 211 : size_t _reservedSize; 212 : //! Allocation overhead (in bytes) required to maintain all blocks. 213 : size_t _overheadSize; 214 : 215 : inline void reset() noexcept { 216 0 : _blockCount = 0; 217 0 : _usedSize = 0; 218 0 : _reservedSize = 0; 219 0 : _overheadSize = 0; 220 : } 221 : 222 : //! Returns count of blocks managed by `JitAllocator` at the moment. 223 : inline size_t blockCount() const noexcept { return _blockCount; } 224 : //! Returns the number of active allocations. 225 : inline size_t allocationCount() const noexcept { return _allocationCount; } 226 : 227 : //! Returns how many bytes are currently used. 228 : inline size_t usedSize() const noexcept { return _usedSize; } 229 : //! Returns the number of bytes unused by the allocator at the moment. 230 : inline size_t unusedSize() const noexcept { return _reservedSize - _usedSize; } 231 : //! Returns the total number of bytes bytes reserved by the allocator (sum of sizes of all blocks). 232 : inline size_t reservedSize() const noexcept { return _reservedSize; } 233 : //! Returns the number of bytes the allocator needs to manage the allocated memory. 234 : inline size_t overheadSize() const noexcept { return _overheadSize; } 235 : 236 : inline double usedSizeAsPercent() const noexcept { 237 : return (double(usedSize()) / (double(reservedSize()) + 1e-16)) * 100.0; 238 : } 239 : 240 : inline double unusedSizeAsPercent() const noexcept { 241 : return (double(unusedSize()) / (double(reservedSize()) + 1e-16)) * 100.0; 242 : } 243 : 244 : inline double overheadSizeAsPercent() const noexcept { 245 : return (double(overheadSize()) / (double(reservedSize()) + 1e-16)) * 100.0; 246 : } 247 : }; 248 : 249 : //! Returns JIT allocator statistics. 250 : //! 251 : //! \remarks This function is thread-safe. 252 : ASMJIT_API Statistics statistics() const noexcept; 253 : 254 : //! \} 255 : }; 256 : 257 : //! \} 258 : 259 : ASMJIT_END_NAMESPACE 260 : 261 : #endif 262 : #endif 263 : 264 : #endif // __PLUMED_HAS_ASMJIT