LCOV - code coverage report
Current view: top level - asmjit - jitallocator.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage (other modules) Lines: 208 336 61.9 %
Date: 2026-11-05 10:19:06 Functions: 13 19 68.4 %

          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             : #include "api-build_p.h"
       8             : #ifndef ASMJIT_NO_JIT
       9             : 
      10             : #include "archtraits.h"
      11             : #include "jitallocator.h"
      12             : #include "osutils_p.h"
      13             : #include "support.h"
      14             : #include "virtmem.h"
      15             : #include "zone.h"
      16             : #include "zonelist.h"
      17             : #include "zonetree.h"
      18             : 
      19             : ASMJIT_BEGIN_NAMESPACE
      20             : 
      21             : // JitAllocator - Constants
      22             : // ========================
      23             : 
      24             : //! Number of pools to use when `JitAllocatorOptions::kUseMultiplePools` is set.
      25             : //!
      26             : //! Each pool increases granularity twice to make memory management more
      27             : //! efficient. Ideal number of pools appears to be 3 to 4 as it distributes
      28             : //! small and large functions properly.
      29             : static constexpr uint32_t kJitAllocatorMultiPoolCount = 3;
      30             : 
      31             : //! Minimum granularity (and the default granularity for pool #0).
      32             : static constexpr uint32_t kJitAllocatorBaseGranularity = 64;
      33             : 
      34             : //! Maximum block size (32MB).
      35             : static constexpr uint32_t kJitAllocatorMaxBlockSize = 1024 * 1024 * 32;
      36             : 
      37             : // JitAllocator - Fill Pattern
      38             : // ===========================
      39             : 
      40             : static inline uint32_t JitAllocator_defaultFillPattern() noexcept {
      41             :   // X86 and X86_64 - 4x 'int3' instruction.
      42             :   if (ASMJIT_ARCH_X86)
      43             :     return 0xCCCCCCCCu;
      44             : 
      45             :   // Unknown...
      46             :   return 0u;
      47             : }
      48             : 
      49             : // JitAllocator - BitVectorRangeIterator
      50             : // =====================================
      51             : 
      52             : template<typename T, uint32_t B>
      53             : class BitVectorRangeIterator {
      54             : public:
      55             :   const T* _ptr;
      56             :   size_t _idx;
      57             :   size_t _end;
      58             :   T _bitWord;
      59             : 
      60             :   enum : uint32_t { kBitWordSize = Support::bitSizeOf<T>() };
      61             :   enum : T { kXorMask = B == 0 ? Support::allOnes<T>() : T(0) };
      62             : 
      63             :   ASMJIT_FORCE_INLINE BitVectorRangeIterator(const T* data, size_t numBitWords) noexcept {
      64             :     init(data, numBitWords);
      65             :   }
      66             : 
      67             :   ASMJIT_FORCE_INLINE BitVectorRangeIterator(const T* data, size_t numBitWords, size_t start, size_t end) noexcept {
      68             :     init(data, numBitWords, start, end);
      69             :   }
      70             : 
      71             :   ASMJIT_FORCE_INLINE void init(const T* data, size_t numBitWords) noexcept {
      72           0 :     init(data, numBitWords, 0, numBitWords * kBitWordSize);
      73             :   }
      74             : 
      75             :   ASMJIT_FORCE_INLINE void init(const T* data, size_t numBitWords, size_t start, size_t end) noexcept {
      76             :     ASMJIT_ASSERT(numBitWords >= (end + kBitWordSize - 1) / kBitWordSize);
      77             :     DebugUtils::unused(numBitWords);
      78             : 
      79             :     size_t idx = Support::alignDown(start, kBitWordSize);
      80         100 :     const T* ptr = data + (idx / kBitWordSize);
      81             : 
      82             :     T bitWord = 0;
      83         100 :     if (idx < end)
      84         100 :       bitWord = (*ptr ^ kXorMask) & (Support::allOnes<T>() << (start % kBitWordSize));
      85             : 
      86             :     _ptr = ptr;
      87             :     _idx = idx;
      88             :     _end = end;
      89             :     _bitWord = bitWord;
      90             :   }
      91             : 
      92             :   ASMJIT_FORCE_INLINE bool nextRange(size_t* rangeStart, size_t* rangeEnd, size_t rangeHint = std::numeric_limits<size_t>::max()) noexcept {
      93             :     // Skip all empty BitWords.
      94         100 :     while (_bitWord == 0) {
      95           0 :       _idx += kBitWordSize;
      96           0 :       if (_idx >= _end)
      97             :         return false;
      98           0 :       _bitWord = (*++_ptr) ^ kXorMask;
      99             :     }
     100             : 
     101         100 :     size_t i = Support::ctz(_bitWord);
     102             : 
     103         100 :     *rangeStart = _idx + i;
     104         100 :     _bitWord = ~(_bitWord ^ ~(Support::allOnes<T>() << i));
     105             : 
     106         100 :     if (_bitWord == 0) {
     107         100 :       *rangeEnd = Support::min(_idx + kBitWordSize, _end);
     108         100 :       while (*rangeEnd - *rangeStart < rangeHint) {
     109           0 :         _idx += kBitWordSize;
     110           0 :         if (_idx >= _end)
     111             :           break;
     112             : 
     113           0 :         _bitWord = (*++_ptr) ^ kXorMask;
     114           0 :         if (_bitWord != Support::allOnes<T>()) {
     115           0 :           size_t j = Support::ctz(~_bitWord);
     116           0 :           *rangeEnd = Support::min(_idx + j, _end);
     117           0 :           _bitWord = _bitWord ^ ~(Support::allOnes<T>() << j);
     118           0 :           break;
     119             :         }
     120             : 
     121           0 :         *rangeEnd = Support::min(_idx + kBitWordSize, _end);
     122             :         _bitWord = 0;
     123           0 :         continue;
     124             :       }
     125             : 
     126             :       return true;
     127             :     }
     128             :     else {
     129           0 :       size_t j = Support::ctz(_bitWord);
     130           0 :       *rangeEnd = Support::min(_idx + j, _end);
     131             : 
     132           0 :       _bitWord = ~(_bitWord ^ ~(Support::allOnes<T>() << j));
     133           0 :       return true;
     134             :     }
     135             :   }
     136             : };
     137             : 
     138             : // JitAllocator - Pool
     139             : // ===================
     140             : 
     141             : class JitAllocatorBlock;
     142             : 
     143             : class JitAllocatorPool {
     144             : public:
     145             :   ASMJIT_NONCOPYABLE(JitAllocatorPool)
     146             : 
     147             :   //! Double linked list of blocks.
     148             :   ZoneList<JitAllocatorBlock> blocks;
     149             :   //! Where to start looking first.
     150             :   JitAllocatorBlock* cursor;
     151             : 
     152             :   //! Count of blocks.
     153             :   uint32_t blockCount;
     154             :   //! Allocation granularity.
     155             :   uint16_t granularity;
     156             :   //! Log2(granularity).
     157             :   uint8_t granularityLog2;
     158             :   //! Count of empty blocks (either 0 or 1 as we won't keep more blocks empty).
     159             :   uint8_t emptyBlockCount;
     160             : 
     161             :   //! Number of bits reserved across all blocks.
     162             :   size_t totalAreaSize;
     163             :   //! Number of bits used across all blocks.
     164             :   size_t totalAreaUsed;
     165             :   //! Overhead of all blocks (in bytes).
     166             :   size_t totalOverheadBytes;
     167             : 
     168             :   inline JitAllocatorPool(uint32_t granularity) noexcept
     169       27422 :     : blocks(),
     170       27422 :       cursor(nullptr),
     171       27422 :       blockCount(0),
     172       27422 :       granularity(uint16_t(granularity)),
     173       27422 :       granularityLog2(uint8_t(Support::ctz(granularity))),
     174       27422 :       emptyBlockCount(0),
     175       27422 :       totalAreaSize(0),
     176       27422 :       totalAreaUsed(0),
     177       27422 :       totalOverheadBytes(0) {}
     178             : 
     179             :   inline void reset() noexcept {
     180             :     blocks.reset();
     181       27422 :     cursor = nullptr;
     182       27422 :     blockCount = 0;
     183       27422 :     totalAreaSize = 0;
     184       27422 :     totalAreaUsed = 0;
     185       27422 :     totalOverheadBytes = 0;
     186             :   }
     187             : 
     188       27492 :   inline size_t byteSizeFromAreaSize(uint32_t areaSize) const noexcept { return size_t(areaSize) * granularity; }
     189       27492 :   inline uint32_t areaSizeFromByteSize(size_t size) const noexcept { return uint32_t((size + granularity - 1) >> granularityLog2); }
     190             : 
     191             :   inline size_t bitWordCountFromAreaSize(uint32_t areaSize) const noexcept {
     192             :     using namespace Support;
     193         100 :     return alignUp<size_t>(areaSize, kBitWordSizeInBits) / kBitWordSizeInBits;
     194             :   }
     195             : };
     196             : 
     197             : // JitAllocator - Block
     198             : // ====================
     199             : 
     200             : class JitAllocatorBlock : public ZoneTreeNodeT<JitAllocatorBlock>,
     201             :                           public ZoneListNode<JitAllocatorBlock> {
     202             : public:
     203             :   ASMJIT_NONCOPYABLE(JitAllocatorBlock)
     204             : 
     205             :   enum Flags : uint32_t {
     206             :     //! Block is empty.
     207             :     kFlagEmpty = 0x00000001u,
     208             :     //! Block is dirty (largestUnusedArea, searchStart, searchEnd).
     209             :     kFlagDirty = 0x00000002u,
     210             :     //! Block is dual-mapped.
     211             :     kFlagDualMapped = 0x00000004u
     212             :   };
     213             : 
     214             :   //! Link to the pool that owns this block.
     215             :   JitAllocatorPool* _pool;
     216             :   //! Virtual memory mapping - either single mapping (both pointers equal) or
     217             :   //! dual mapping, where one pointer is Read+Execute and the second Read+Write.
     218             :   VirtMem::DualMapping _mapping;
     219             :   //! Virtual memory size (block size) [bytes].
     220             :   size_t _blockSize;
     221             : 
     222             :   //! Block flags.
     223             :   uint32_t _flags;
     224             :   //! Size of the whole block area (bit-vector size).
     225             :   uint32_t _areaSize;
     226             :   //! Used area (number of bits in bit-vector used).
     227             :   uint32_t _areaUsed;
     228             :   //! The largest unused continuous area in the bit-vector (or `areaSize` to initiate rescan).
     229             :   uint32_t _largestUnusedArea;
     230             :   //! Start of a search range (for unused bits).
     231             :   uint32_t _searchStart;
     232             :   //! End of a search range (for unused bits).
     233             :   uint32_t _searchEnd;
     234             : 
     235             :   //! Used bit-vector (0 = unused, 1 = used).
     236             :   Support::BitWord* _usedBitVector;
     237             :   //! Stop bit-vector (0 = don't care, 1 = stop).
     238             :   Support::BitWord* _stopBitVector;
     239             : 
     240             :   inline JitAllocatorBlock(
     241             :     JitAllocatorPool* pool,
     242             :     VirtMem::DualMapping mapping,
     243             :     size_t blockSize,
     244             :     uint32_t blockFlags,
     245             :     Support::BitWord* usedBitVector,
     246             :     Support::BitWord* stopBitVector,
     247             :     uint32_t areaSize) noexcept
     248       27392 :     : ZoneTreeNodeT(),
     249       27392 :       _pool(pool),
     250       27392 :       _mapping(mapping),
     251       27392 :       _blockSize(blockSize),
     252       27392 :       _flags(blockFlags),
     253       27392 :       _areaSize(areaSize),
     254       27392 :       _areaUsed(0),
     255       27392 :       _largestUnusedArea(areaSize),
     256       27392 :       _searchStart(0),
     257       27392 :       _searchEnd(areaSize),
     258       27392 :       _usedBitVector(usedBitVector),
     259       27392 :       _stopBitVector(stopBitVector) {}
     260             : 
     261       27492 :   inline JitAllocatorPool* pool() const noexcept { return _pool; }
     262             : 
     263       55084 :   inline uint8_t* rxPtr() const noexcept { return static_cast<uint8_t*>(_mapping.rx); }
     264           0 :   inline uint8_t* rwPtr() const noexcept { return static_cast<uint8_t*>(_mapping.rw); }
     265             : 
     266       27592 :   inline bool hasFlag(uint32_t f) const noexcept { return (_flags & f) != 0; }
     267       27492 :   inline void addFlags(uint32_t f) noexcept { _flags |= f; }
     268         200 :   inline void clearFlags(uint32_t f) noexcept { _flags &= ~f; }
     269             : 
     270             :   inline bool isDirty() const noexcept { return hasFlag(kFlagDirty); }
     271             :   inline void makeDirty() noexcept { addFlags(kFlagDirty); }
     272             : 
     273           0 :   inline size_t blockSize() const noexcept { return _blockSize; }
     274             : 
     275           0 :   inline uint32_t areaSize() const noexcept { return _areaSize; }
     276         200 :   inline uint32_t areaUsed() const noexcept { return _areaUsed; }
     277       27592 :   inline uint32_t areaAvailable() const noexcept { return _areaSize - _areaUsed; }
     278         100 :   inline uint32_t largestUnusedArea() const noexcept { return _largestUnusedArea; }
     279             : 
     280             :   inline void decreaseUsedArea(uint32_t value) noexcept {
     281             :     _areaUsed -= value;
     282             :     _pool->totalAreaUsed -= value;
     283             :   }
     284             : 
     285       27492 :   inline void markAllocatedArea(uint32_t allocatedAreaStart, uint32_t allocatedAreaEnd) noexcept {
     286       27492 :     uint32_t allocatedAreaSize = allocatedAreaEnd - allocatedAreaStart;
     287             : 
     288             :     // Mark the newly allocated space as occupied and also the sentinel.
     289       27492 :     Support::bitVectorFill(_usedBitVector, allocatedAreaStart, allocatedAreaSize);
     290       27492 :     Support::bitVectorSetBit(_stopBitVector, allocatedAreaEnd - 1, true);
     291             : 
     292             :     // Update search region and statistics.
     293       27492 :     _pool->totalAreaUsed += allocatedAreaSize;
     294       27492 :     _areaUsed += allocatedAreaSize;
     295             : 
     296       27492 :     if (areaAvailable() == 0) {
     297           0 :       _searchStart = _areaSize;
     298           0 :       _searchEnd = 0;
     299           0 :       _largestUnusedArea = 0;
     300             :       clearFlags(kFlagDirty);
     301             :     }
     302             :     else {
     303       27492 :       if (_searchStart == allocatedAreaStart)
     304         100 :         _searchStart = allocatedAreaEnd;
     305       27492 :       if (_searchEnd == allocatedAreaEnd)
     306           0 :         _searchEnd = allocatedAreaStart;
     307             :       addFlags(kFlagDirty);
     308             :     }
     309       27492 :   }
     310             : 
     311         100 :   inline void markReleasedArea(uint32_t releasedAreaStart, uint32_t releasedAreaEnd) noexcept {
     312         100 :     uint32_t releasedAreaSize = releasedAreaEnd - releasedAreaStart;
     313             : 
     314             :     // Update the search region and statistics.
     315         100 :     _pool->totalAreaUsed -= releasedAreaSize;
     316         100 :     _areaUsed -= releasedAreaSize;
     317         100 :     _searchStart = Support::min(_searchStart, releasedAreaStart);
     318         100 :     _searchEnd = Support::max(_searchEnd, releasedAreaEnd);
     319             : 
     320             :     // Unmark occupied bits and also the sentinel.
     321         100 :     Support::bitVectorClear(_usedBitVector, releasedAreaStart, releasedAreaSize);
     322         100 :     Support::bitVectorSetBit(_stopBitVector, releasedAreaEnd - 1, false);
     323             : 
     324         100 :     if (areaUsed() == 0) {
     325         100 :       _searchStart = 0;
     326         100 :       _searchEnd = _areaSize;
     327         100 :       _largestUnusedArea = _areaSize;
     328             :       addFlags(kFlagEmpty);
     329             :       clearFlags(kFlagDirty);
     330             :     }
     331             :     else {
     332             :       addFlags(kFlagDirty);
     333             :     }
     334         100 :   }
     335             : 
     336           0 :   inline void markShrunkArea(uint32_t shrunkAreaStart, uint32_t shrunkAreaEnd) noexcept {
     337           0 :     uint32_t shrunkAreaSize = shrunkAreaEnd - shrunkAreaStart;
     338             : 
     339             :     // Shrunk area cannot start at zero as it would mean that we have shrunk the first
     340             :     // block to zero bytes, which is not allowed as such block must be released instead.
     341             :     ASMJIT_ASSERT(shrunkAreaStart != 0);
     342             :     ASMJIT_ASSERT(shrunkAreaSize != 0);
     343             : 
     344             :     // Update the search region and statistics.
     345           0 :     _pool->totalAreaUsed -= shrunkAreaSize;
     346           0 :     _areaUsed -= shrunkAreaSize;
     347           0 :     _searchStart = Support::min(_searchStart, shrunkAreaStart);
     348           0 :     _searchEnd = Support::max(_searchEnd, shrunkAreaEnd);
     349             : 
     350             :     // Unmark the released space and move the sentinel.
     351           0 :     Support::bitVectorClear(_usedBitVector, shrunkAreaStart, shrunkAreaSize);
     352           0 :     Support::bitVectorSetBit(_stopBitVector, shrunkAreaEnd - 1, false);
     353           0 :     Support::bitVectorSetBit(_stopBitVector, shrunkAreaStart - 1, true);
     354             : 
     355             :     addFlags(kFlagDirty);
     356           0 :   }
     357             : 
     358             :   // RBTree default CMP uses '<' and '>' operators.
     359           0 :   inline bool operator<(const JitAllocatorBlock& other) const noexcept { return rxPtr() < other.rxPtr(); }
     360           0 :   inline bool operator>(const JitAllocatorBlock& other) const noexcept { return rxPtr() > other.rxPtr(); }
     361             : 
     362             :   // Special implementation for querying blocks by `key`, which must be in `[BlockPtr, BlockPtr + BlockSize)` range.
     363         100 :   inline bool operator<(const uint8_t* key) const noexcept { return rxPtr() + _blockSize <= key; }
     364         100 :   inline bool operator>(const uint8_t* key) const noexcept { return rxPtr() > key; }
     365             : };
     366             : 
     367             : // JitAllocator - PrivateImpl
     368             : // ==========================
     369             : 
     370             : class JitAllocatorPrivateImpl : public JitAllocator::Impl {
     371             : public:
     372             :   //! Lock for thread safety.
     373             :   mutable Lock lock;
     374             :   //! System page size (also a minimum block size).
     375             :   uint32_t pageSize;
     376             :   //! Number of active allocations.
     377             :   size_t allocationCount;
     378             : 
     379             :   //! Blocks from all pools in RBTree.
     380             :   ZoneTree<JitAllocatorBlock> tree;
     381             :   //! Allocator pools.
     382             :   JitAllocatorPool* pools;
     383             :   //! Number of allocator pools.
     384             :   size_t poolCount;
     385             : 
     386             :   inline JitAllocatorPrivateImpl(JitAllocatorPool* pools, size_t poolCount) noexcept
     387       27422 :     : JitAllocator::Impl {},
     388             :       pageSize(0),
     389       27422 :       allocationCount(0),
     390       27422 :       pools(pools),
     391       27422 :       poolCount(poolCount) {}
     392       27422 :   inline ~JitAllocatorPrivateImpl() noexcept {}
     393             : };
     394             : 
     395             : static const JitAllocator::Impl JitAllocatorImpl_none {};
     396             : static const JitAllocator::CreateParams JitAllocatorParams_none {};
     397             : 
     398             : // JitAllocator - Utilities
     399             : // ========================
     400             : 
     401       27422 : static inline JitAllocatorPrivateImpl* JitAllocatorImpl_new(const JitAllocator::CreateParams* params) noexcept {
     402       27422 :   VirtMem::Info vmInfo = VirtMem::info();
     403             : 
     404       27422 :   if (!params)
     405             :     params = &JitAllocatorParams_none;
     406             : 
     407       27422 :   JitAllocatorOptions options = params->options;
     408       27422 :   uint32_t blockSize = params->blockSize;
     409       27422 :   uint32_t granularity = params->granularity;
     410       27422 :   uint32_t fillPattern = params->fillPattern;
     411             : 
     412             :   // Setup pool count to [1..3].
     413             :   size_t poolCount = 1;
     414       27422 :   if (Support::test(options, JitAllocatorOptions::kUseMultiplePools))
     415             :     poolCount = kJitAllocatorMultiPoolCount;;
     416             : 
     417             :   // Setup block size [64kB..256MB].
     418       27422 :   if (blockSize < 64 * 1024 || blockSize > 256 * 1024 * 1024 || !Support::isPowerOf2(blockSize))
     419       27422 :     blockSize = vmInfo.pageGranularity;
     420             : 
     421             :   // Setup granularity [64..256].
     422       27422 :   if (granularity < 64 || granularity > 256 || !Support::isPowerOf2(granularity))
     423             :     granularity = kJitAllocatorBaseGranularity;
     424             : 
     425             :   // Setup fill-pattern.
     426       27422 :   if (uint32_t(options & JitAllocatorOptions::kCustomFillPattern) == 0)
     427             :     fillPattern = JitAllocator_defaultFillPattern();
     428             : 
     429       27422 :   size_t size = sizeof(JitAllocatorPrivateImpl) + sizeof(JitAllocatorPool) * poolCount;
     430       27422 :   void* p = ::malloc(size);
     431       27422 :   if (ASMJIT_UNLIKELY(!p))
     432             :     return nullptr;
     433             : 
     434       27422 :   JitAllocatorPool* pools = reinterpret_cast<JitAllocatorPool*>((uint8_t*)p + sizeof(JitAllocatorPrivateImpl));
     435             :   JitAllocatorPrivateImpl* impl = new(p) JitAllocatorPrivateImpl(pools, poolCount);
     436             : 
     437       27422 :   impl->options = options;
     438       27422 :   impl->blockSize = blockSize;
     439       27422 :   impl->granularity = granularity;
     440       27422 :   impl->fillPattern = fillPattern;
     441       27422 :   impl->pageSize = vmInfo.pageSize;
     442             : 
     443       54844 :   for (size_t poolId = 0; poolId < poolCount; poolId++)
     444       27422 :     new(&pools[poolId]) JitAllocatorPool(granularity << poolId);
     445             : 
     446             :   return impl;
     447             : }
     448             : 
     449             : static inline void JitAllocatorImpl_destroy(JitAllocatorPrivateImpl* impl) noexcept {
     450             :   impl->~JitAllocatorPrivateImpl();
     451       27422 :   ::free(impl);
     452             : }
     453             : 
     454       27492 : static inline size_t JitAllocatorImpl_sizeToPoolId(const JitAllocatorPrivateImpl* impl, size_t size) noexcept {
     455       27492 :   size_t poolId = impl->poolCount - 1;
     456       27492 :   size_t granularity = size_t(impl->granularity) << poolId;
     457             : 
     458       27492 :   while (poolId) {
     459           0 :     if (Support::alignUp(size, granularity) == size)
     460             :       break;
     461           0 :     poolId--;
     462           0 :     granularity >>= 1;
     463             :   }
     464             : 
     465       27492 :   return poolId;
     466             : }
     467             : 
     468             : static inline size_t JitAllocatorImpl_bitVectorSizeToByteSize(uint32_t areaSize) noexcept {
     469             :   using Support::kBitWordSizeInBits;
     470       27392 :   return ((areaSize + kBitWordSizeInBits - 1u) / kBitWordSizeInBits) * sizeof(Support::BitWord);
     471             : }
     472             : 
     473       27392 : static inline size_t JitAllocatorImpl_calculateIdealBlockSize(JitAllocatorPrivateImpl* impl, JitAllocatorPool* pool, size_t allocationSize) noexcept {
     474             :   JitAllocatorBlock* last = pool->blocks.last();
     475       27392 :   size_t blockSize = last ? last->blockSize() : size_t(impl->blockSize);
     476             : 
     477       27392 :   if (blockSize < kJitAllocatorMaxBlockSize)
     478       27392 :     blockSize *= 2u;
     479             : 
     480       27392 :   if (allocationSize > blockSize) {
     481           0 :     blockSize = Support::alignUp(allocationSize, impl->blockSize);
     482           0 :     if (ASMJIT_UNLIKELY(blockSize < allocationSize))
     483           0 :       return 0; // Overflown.
     484             :   }
     485             : 
     486             :   return blockSize;
     487             : }
     488             : 
     489           0 : ASMJIT_FAVOR_SPEED static void JitAllocatorImpl_fillPattern(void* mem, uint32_t pattern, size_t sizeInBytes) noexcept {
     490           0 :   size_t n = sizeInBytes / 4u;
     491             :   uint32_t* p = static_cast<uint32_t*>(mem);
     492             : 
     493           0 :   for (size_t i = 0; i < n; i++)
     494           0 :     p[i] = pattern;
     495           0 : }
     496             : 
     497             : // Allocate a new `JitAllocatorBlock` for the given `blockSize`.
     498             : //
     499             : // NOTE: The block doesn't have `kFlagEmpty` flag set, because the new block
     500             : // is only allocated when it's actually needed, so it would be cleared anyway.
     501       27392 : static JitAllocatorBlock* JitAllocatorImpl_newBlock(JitAllocatorPrivateImpl* impl, JitAllocatorPool* pool, size_t blockSize) noexcept {
     502             :   using Support::BitWord;
     503             :   using Support::kBitWordSizeInBits;
     504             : 
     505       27392 :   uint32_t areaSize = uint32_t((blockSize + pool->granularity - 1) >> pool->granularityLog2);
     506       27392 :   uint32_t numBitWords = (areaSize + kBitWordSizeInBits - 1u) / kBitWordSizeInBits;
     507             : 
     508       27392 :   JitAllocatorBlock* block = static_cast<JitAllocatorBlock*>(::malloc(sizeof(JitAllocatorBlock)));
     509             :   BitWord* bitWords = nullptr;
     510       27392 :   VirtMem::DualMapping virtMem {};
     511             :   Error err = kErrorOutOfMemory;
     512             : 
     513       27392 :   if (block != nullptr)
     514       27392 :     bitWords = static_cast<BitWord*>(::malloc(size_t(numBitWords) * 2 * sizeof(BitWord)));
     515             : 
     516             :   uint32_t blockFlags = 0;
     517       27392 :   if (bitWords != nullptr) {
     518       27392 :     if (Support::test(impl->options, JitAllocatorOptions::kUseDualMapping)) {
     519           0 :       err = VirtMem::allocDualMapping(&virtMem, blockSize, VirtMem::MemoryFlags::kAccessRWX);
     520             :       blockFlags |= JitAllocatorBlock::kFlagDualMapped;
     521             :     }
     522             :     else {
     523       27392 :       err = VirtMem::alloc(&virtMem.rx, blockSize, VirtMem::MemoryFlags::kAccessRWX);
     524       27392 :       virtMem.rw = virtMem.rx;
     525             :     }
     526             :   }
     527             : 
     528             :   // Out of memory.
     529       27392 :   if (ASMJIT_UNLIKELY(!block || !bitWords || err != kErrorOk)) {
     530           0 :     if (bitWords)
     531           0 :       ::free(bitWords);
     532             : 
     533           0 :     if (block)
     534           0 :       ::free(block);
     535             : 
     536           0 :     return nullptr;
     537             :   }
     538             : 
     539             :   // Fill the memory if the secure mode is enabled.
     540       27392 :   if (Support::test(impl->options, JitAllocatorOptions::kFillUnusedMemory)) {
     541           0 :     VirtMem::ProtectJitReadWriteScope scope(virtMem.rw, blockSize);
     542           0 :     JitAllocatorImpl_fillPattern(virtMem.rw, impl->fillPattern, blockSize);
     543             :   }
     544             : 
     545       27392 :   memset(bitWords, 0, size_t(numBitWords) * 2 * sizeof(BitWord));
     546       27392 :   return new(block) JitAllocatorBlock(pool, virtMem, blockSize, blockFlags, bitWords, bitWords + numBitWords, areaSize);
     547             : }
     548             : 
     549       27392 : static void JitAllocatorImpl_deleteBlock(JitAllocatorPrivateImpl* impl, JitAllocatorBlock* block) noexcept {
     550             :   DebugUtils::unused(impl);
     551             : 
     552       27392 :   if (block->hasFlag(JitAllocatorBlock::kFlagDualMapped))
     553           0 :     VirtMem::releaseDualMapping(&block->_mapping, block->blockSize());
     554             :   else
     555       27392 :     VirtMem::release(block->rxPtr(), block->blockSize());
     556             : 
     557       27392 :   ::free(block->_usedBitVector);
     558       27392 :   ::free(block);
     559       27392 : }
     560             : 
     561       27392 : static void JitAllocatorImpl_insertBlock(JitAllocatorPrivateImpl* impl, JitAllocatorBlock* block) noexcept {
     562             :   JitAllocatorPool* pool = block->pool();
     563             : 
     564       27392 :   if (!pool->cursor)
     565       27392 :     pool->cursor = block;
     566             : 
     567             :   // Add to RBTree and List.
     568       27392 :   impl->tree.insert(block);
     569             :   pool->blocks.append(block);
     570             : 
     571             :   // Update statistics.
     572       27392 :   pool->blockCount++;
     573       27392 :   pool->totalAreaSize += block->areaSize();
     574       27392 :   pool->totalOverheadBytes += sizeof(JitAllocatorBlock) + JitAllocatorImpl_bitVectorSizeToByteSize(block->areaSize()) * 2u;
     575       27392 : }
     576             : 
     577           0 : static void JitAllocatorImpl_removeBlock(JitAllocatorPrivateImpl* impl, JitAllocatorBlock* block) noexcept {
     578             :   JitAllocatorPool* pool = block->pool();
     579             : 
     580             :   // Remove from RBTree and List.
     581           0 :   if (pool->cursor == block)
     582           0 :     pool->cursor = block->hasPrev() ? block->prev() : block->next();
     583             : 
     584           0 :   impl->tree.remove(block);
     585           0 :   pool->blocks.unlink(block);
     586             : 
     587             :   // Update statistics.
     588           0 :   pool->blockCount--;
     589           0 :   pool->totalAreaSize -= block->areaSize();
     590           0 :   pool->totalOverheadBytes -= sizeof(JitAllocatorBlock) + JitAllocatorImpl_bitVectorSizeToByteSize(block->areaSize()) * 2u;
     591           0 : }
     592             : 
     593           0 : static void JitAllocatorImpl_wipeOutBlock(JitAllocatorPrivateImpl* impl, JitAllocatorBlock* block) noexcept {
     594           0 :   if (block->hasFlag(JitAllocatorBlock::kFlagEmpty))
     595             :     return;
     596             : 
     597             :   JitAllocatorPool* pool = block->pool();
     598             :   uint32_t areaSize = block->areaSize();
     599           0 :   uint32_t granularity = pool->granularity;
     600             :   size_t numBitWords = pool->bitWordCountFromAreaSize(areaSize);
     601             : 
     602           0 :   VirtMem::protectJitMemory(VirtMem::ProtectJitAccess::kReadWrite);
     603           0 :   if (Support::test(impl->options, JitAllocatorOptions::kFillUnusedMemory)) {
     604             :     uint8_t* rwPtr = block->rwPtr();
     605           0 :     BitVectorRangeIterator<Support::BitWord, 0> it(block->_usedBitVector, pool->bitWordCountFromAreaSize(block->areaSize()));
     606             : 
     607             :     size_t rangeStart;
     608             :     size_t rangeEnd;
     609             : 
     610           0 :     while (it.nextRange(&rangeStart, &rangeEnd)) {
     611           0 :       uint8_t* spanPtr = rwPtr + rangeStart * granularity;
     612           0 :       size_t spanSize = (rangeEnd - rangeStart) * granularity;
     613             : 
     614           0 :       JitAllocatorImpl_fillPattern(spanPtr, impl->fillPattern, spanSize);
     615           0 :       VirtMem::flushInstructionCache(spanPtr, spanSize);
     616             :     }
     617             :   }
     618           0 :   VirtMem::protectJitMemory(VirtMem::ProtectJitAccess::kReadExecute);
     619             : 
     620           0 :   memset(block->_usedBitVector, 0, size_t(numBitWords) * sizeof(Support::BitWord));
     621           0 :   memset(block->_stopBitVector, 0, size_t(numBitWords) * sizeof(Support::BitWord));
     622             : 
     623           0 :   block->_areaUsed = 0;
     624           0 :   block->_largestUnusedArea = areaSize;
     625           0 :   block->_searchStart = 0;
     626           0 :   block->_searchEnd = areaSize;
     627             :   block->addFlags(JitAllocatorBlock::kFlagEmpty);
     628             :   block->clearFlags(JitAllocatorBlock::kFlagDirty);
     629             : }
     630             : 
     631             : // JitAllocator - Construction & Destruction
     632             : // =========================================
     633             : 
     634       27422 : JitAllocator::JitAllocator(const CreateParams* params) noexcept {
     635       27422 :   _impl = JitAllocatorImpl_new(params);
     636       27422 :   if (ASMJIT_UNLIKELY(!_impl))
     637           0 :     _impl = const_cast<JitAllocator::Impl*>(&JitAllocatorImpl_none);
     638       27422 : }
     639             : 
     640       54844 : JitAllocator::~JitAllocator() noexcept {
     641       27422 :   if (_impl == &JitAllocatorImpl_none)
     642             :     return;
     643             : 
     644       27422 :   reset(ResetPolicy::kHard);
     645       27422 :   JitAllocatorImpl_destroy(static_cast<JitAllocatorPrivateImpl*>(_impl));
     646       27422 : }
     647             : 
     648             : // JitAllocator - Reset
     649             : // ====================
     650             : 
     651       27422 : void JitAllocator::reset(ResetPolicy resetPolicy) noexcept {
     652       27422 :   if (_impl == &JitAllocatorImpl_none)
     653             :     return;
     654             : 
     655             :   JitAllocatorPrivateImpl* impl = static_cast<JitAllocatorPrivateImpl*>(_impl);
     656             :   impl->tree.reset();
     657       27422 :   size_t poolCount = impl->poolCount;
     658             : 
     659       54844 :   for (size_t poolId = 0; poolId < poolCount; poolId++) {
     660       27422 :     JitAllocatorPool& pool = impl->pools[poolId];
     661             :     JitAllocatorBlock* block = pool.blocks.first();
     662             : 
     663             :     JitAllocatorBlock* blockToKeep = nullptr;
     664       27422 :     if (resetPolicy != ResetPolicy::kHard && uint32_t(impl->options & JitAllocatorOptions::kImmediateRelease) == 0) {
     665             :       blockToKeep = block;
     666             :       block = block->next();
     667             :     }
     668             : 
     669       54814 :     while (block) {
     670             :       JitAllocatorBlock* next = block->next();
     671       27392 :       JitAllocatorImpl_deleteBlock(impl, block);
     672             :       block = next;
     673             :     }
     674             : 
     675             :     pool.reset();
     676             : 
     677       27422 :     if (blockToKeep) {
     678           0 :       blockToKeep->_listNodes[0] = nullptr;
     679           0 :       blockToKeep->_listNodes[1] = nullptr;
     680           0 :       JitAllocatorImpl_wipeOutBlock(impl, blockToKeep);
     681           0 :       JitAllocatorImpl_insertBlock(impl, blockToKeep);
     682           0 :       pool.emptyBlockCount = 1;
     683             :     }
     684             :   }
     685             : }
     686             : 
     687             : // JitAllocator - Statistics
     688             : // =========================
     689             : 
     690           0 : JitAllocator::Statistics JitAllocator::statistics() const noexcept {
     691             :   Statistics statistics;
     692             :   statistics.reset();
     693             : 
     694           0 :   if (ASMJIT_LIKELY(_impl != &JitAllocatorImpl_none)) {
     695             :     JitAllocatorPrivateImpl* impl = static_cast<JitAllocatorPrivateImpl*>(_impl);
     696             :     LockGuard guard(impl->lock);
     697             : 
     698           0 :     size_t poolCount = impl->poolCount;
     699           0 :     for (size_t poolId = 0; poolId < poolCount; poolId++) {
     700           0 :       const JitAllocatorPool& pool = impl->pools[poolId];
     701           0 :       statistics._blockCount   += size_t(pool.blockCount);
     702           0 :       statistics._reservedSize += size_t(pool.totalAreaSize) * pool.granularity;
     703           0 :       statistics._usedSize     += size_t(pool.totalAreaUsed) * pool.granularity;
     704           0 :       statistics._overheadSize += size_t(pool.totalOverheadBytes);
     705             :     }
     706             : 
     707           0 :     statistics._allocationCount = impl->allocationCount;
     708             :   }
     709             : 
     710           0 :   return statistics;
     711             : }
     712             : 
     713             : // JitAllocator - Alloc & Release
     714             : // ==============================
     715             : 
     716       27492 : Error JitAllocator::alloc(void** rxPtrOut, void** rwPtrOut, size_t size) noexcept {
     717       27492 :   if (ASMJIT_UNLIKELY(_impl == &JitAllocatorImpl_none))
     718             :     return DebugUtils::errored(kErrorNotInitialized);
     719             : 
     720             :   JitAllocatorPrivateImpl* impl = static_cast<JitAllocatorPrivateImpl*>(_impl);
     721             :   constexpr uint32_t kNoIndex = std::numeric_limits<uint32_t>::max();
     722             : 
     723       27492 :   *rxPtrOut = nullptr;
     724       27492 :   *rwPtrOut = nullptr;
     725             : 
     726             :   // Align to the minimum granularity by default.
     727       27492 :   size = Support::alignUp<size_t>(size, impl->granularity);
     728       27492 :   if (ASMJIT_UNLIKELY(size == 0))
     729             :     return DebugUtils::errored(kErrorInvalidArgument);
     730             : 
     731       27492 :   if (ASMJIT_UNLIKELY(size > std::numeric_limits<uint32_t>::max() / 2))
     732             :     return DebugUtils::errored(kErrorTooLarge);
     733             : 
     734             :   LockGuard guard(impl->lock);
     735       27492 :   JitAllocatorPool* pool = &impl->pools[JitAllocatorImpl_sizeToPoolId(impl, size)];
     736             : 
     737             :   uint32_t areaIndex = kNoIndex;
     738             :   uint32_t areaSize = uint32_t(pool->areaSizeFromByteSize(size));
     739             : 
     740             :   // Try to find the requested memory area in existing blocks.
     741             :   JitAllocatorBlock* block = pool->blocks.first();
     742       27492 :   if (block) {
     743             :     JitAllocatorBlock* initial = block;
     744             :     do {
     745         100 :       JitAllocatorBlock* next = block->hasNext() ? block->next() : pool->blocks.first();
     746         100 :       if (block->areaAvailable() >= areaSize) {
     747         100 :         if (block->isDirty() || block->largestUnusedArea() >= areaSize) {
     748         100 :           BitVectorRangeIterator<Support::BitWord, 0> it(block->_usedBitVector, pool->bitWordCountFromAreaSize(block->areaSize()), block->_searchStart, block->_searchEnd);
     749             : 
     750             :           size_t rangeStart = 0;
     751             :           size_t rangeEnd = block->areaSize();
     752             : 
     753             :           size_t searchStart = SIZE_MAX;
     754             :           size_t largestArea = 0;
     755             : 
     756         100 :           while (it.nextRange(&rangeStart, &rangeEnd, areaSize)) {
     757         100 :             size_t rangeSize = rangeEnd - rangeStart;
     758         100 :             if (rangeSize >= areaSize) {
     759         100 :               areaIndex = uint32_t(rangeStart);
     760             :               break;
     761             :             }
     762             : 
     763             :             searchStart = Support::min(searchStart, rangeStart);
     764             :             largestArea = Support::max(largestArea, rangeSize);
     765             :           }
     766             : 
     767         100 :           if (areaIndex != kNoIndex)
     768             :             break;
     769             : 
     770           0 :           if (searchStart != SIZE_MAX) {
     771             :             // Because we have iterated over the entire block, we can now mark the
     772             :             // largest unused area that can be used to cache the next traversal.
     773             :             size_t searchEnd = rangeEnd;
     774             : 
     775           0 :             block->_searchStart = uint32_t(searchStart);
     776           0 :             block->_searchEnd = uint32_t(searchEnd);
     777           0 :             block->_largestUnusedArea = uint32_t(largestArea);
     778             :             block->clearFlags(JitAllocatorBlock::kFlagDirty);
     779             :           }
     780             :         }
     781             :       }
     782             : 
     783             :       block = next;
     784           0 :     } while (block != initial);
     785             :   }
     786             : 
     787             :   // Allocate a new block if there is no region of a required width.
     788         100 :   if (areaIndex == kNoIndex) {
     789       27392 :     size_t blockSize = JitAllocatorImpl_calculateIdealBlockSize(impl, pool, size);
     790       27392 :     if (ASMJIT_UNLIKELY(!blockSize))
     791             :       return DebugUtils::errored(kErrorOutOfMemory);
     792             : 
     793       27392 :     block = JitAllocatorImpl_newBlock(impl, pool, blockSize);
     794             :     areaIndex = 0;
     795             : 
     796       27392 :     if (ASMJIT_UNLIKELY(!block))
     797             :       return DebugUtils::errored(kErrorOutOfMemory);
     798             : 
     799       27392 :     JitAllocatorImpl_insertBlock(impl, block);
     800       27392 :     block->_searchStart = areaSize;
     801       27392 :     block->_largestUnusedArea = block->areaSize() - areaSize;
     802             :   }
     803         100 :   else if (block->hasFlag(JitAllocatorBlock::kFlagEmpty)) {
     804         100 :     pool->emptyBlockCount--;
     805             :     block->clearFlags(JitAllocatorBlock::kFlagEmpty);
     806             :   }
     807             : 
     808             :   // Update statistics.
     809       27492 :   impl->allocationCount++;
     810       27492 :   block->markAllocatedArea(areaIndex, areaIndex + areaSize);
     811             : 
     812             :   // Return a pointer to the allocated memory.
     813             :   size_t offset = pool->byteSizeFromAreaSize(areaIndex);
     814             :   ASMJIT_ASSERT(offset <= block->blockSize() - size);
     815             : 
     816       27492 :   *rxPtrOut = block->rxPtr() + offset;
     817       27492 :   *rwPtrOut = block->rwPtr() + offset;
     818       27492 :   return kErrorOk;
     819             : }
     820             : 
     821         100 : Error JitAllocator::release(void* rxPtr) noexcept {
     822         100 :   if (ASMJIT_UNLIKELY(_impl == &JitAllocatorImpl_none))
     823             :     return DebugUtils::errored(kErrorNotInitialized);
     824             : 
     825         100 :   if (ASMJIT_UNLIKELY(!rxPtr))
     826             :     return DebugUtils::errored(kErrorInvalidArgument);
     827             : 
     828             :   JitAllocatorPrivateImpl* impl = static_cast<JitAllocatorPrivateImpl*>(_impl);
     829             :   LockGuard guard(impl->lock);
     830             : 
     831         100 :   JitAllocatorBlock* block = impl->tree.get(static_cast<uint8_t*>(rxPtr));
     832         100 :   if (ASMJIT_UNLIKELY(!block))
     833             :     return DebugUtils::errored(kErrorInvalidState);
     834             : 
     835             :   // Offset relative to the start of the block.
     836             :   JitAllocatorPool* pool = block->pool();
     837         100 :   size_t offset = (size_t)((uint8_t*)rxPtr - block->rxPtr());
     838             : 
     839             :   // The first bit representing the allocated area and its size.
     840         100 :   uint32_t areaIndex = uint32_t(offset >> pool->granularityLog2);
     841         100 :   uint32_t areaEnd = uint32_t(Support::bitVectorIndexOf(block->_stopBitVector, areaIndex, true)) + 1;
     842         100 :   uint32_t areaSize = areaEnd - areaIndex;
     843             : 
     844         100 :   impl->allocationCount--;
     845         100 :   block->markReleasedArea(areaIndex, areaEnd);
     846             : 
     847             :   // Fill the released memory if the secure mode is enabled.
     848         100 :   if (Support::test(impl->options, JitAllocatorOptions::kFillUnusedMemory)) {
     849           0 :     uint8_t* spanPtr = block->rwPtr() + areaIndex * pool->granularity;
     850           0 :     size_t spanSize = areaSize * pool->granularity;
     851             : 
     852             :     VirtMem::ProtectJitReadWriteScope scope(spanPtr, spanSize);
     853           0 :     JitAllocatorImpl_fillPattern(spanPtr, impl->fillPattern, spanSize);
     854             :   }
     855             : 
     856             :   // Release the whole block if it became empty.
     857         100 :   if (block->areaUsed() == 0) {
     858         100 :     if (pool->emptyBlockCount || Support::test(impl->options, JitAllocatorOptions::kImmediateRelease)) {
     859           0 :       JitAllocatorImpl_removeBlock(impl, block);
     860           0 :       JitAllocatorImpl_deleteBlock(impl, block);
     861             :     }
     862             :     else {
     863         100 :       pool->emptyBlockCount++;
     864             :     }
     865             :   }
     866             : 
     867             :   return kErrorOk;
     868             : }
     869             : 
     870           0 : Error JitAllocator::shrink(void* rxPtr, size_t newSize) noexcept {
     871           0 :   if (ASMJIT_UNLIKELY(_impl == &JitAllocatorImpl_none))
     872             :     return DebugUtils::errored(kErrorNotInitialized);
     873             : 
     874           0 :   if (ASMJIT_UNLIKELY(!rxPtr))
     875             :     return DebugUtils::errored(kErrorInvalidArgument);
     876             : 
     877           0 :   if (ASMJIT_UNLIKELY(newSize == 0))
     878           0 :     return release(rxPtr);
     879             : 
     880             :   JitAllocatorPrivateImpl* impl = static_cast<JitAllocatorPrivateImpl*>(_impl);
     881             :   LockGuard guard(impl->lock);
     882           0 :   JitAllocatorBlock* block = impl->tree.get(static_cast<uint8_t*>(rxPtr));
     883             : 
     884           0 :   if (ASMJIT_UNLIKELY(!block))
     885             :     return DebugUtils::errored(kErrorInvalidArgument);
     886             : 
     887             :   // Offset relative to the start of the block.
     888             :   JitAllocatorPool* pool = block->pool();
     889           0 :   size_t offset = (size_t)((uint8_t*)rxPtr - block->rxPtr());
     890             : 
     891             :   // The first bit representing the allocated area and its size.
     892           0 :   uint32_t areaStart = uint32_t(offset >> pool->granularityLog2);
     893           0 :   uint32_t areaEnd = uint32_t(Support::bitVectorIndexOf(block->_stopBitVector, areaStart, true)) + 1;
     894             : 
     895           0 :   uint32_t areaPrevSize = areaEnd - areaStart;
     896             :   uint32_t areaShrunkSize = pool->areaSizeFromByteSize(newSize);
     897             : 
     898           0 :   if (ASMJIT_UNLIKELY(areaShrunkSize > areaPrevSize))
     899             :     return DebugUtils::errored(kErrorInvalidState);
     900             : 
     901           0 :   uint32_t areaDiff = areaPrevSize - areaShrunkSize;
     902           0 :   if (areaDiff) {
     903           0 :     block->markShrunkArea(areaStart + areaShrunkSize, areaEnd);
     904             : 
     905             :     // Fill released memory if the secure mode is enabled.
     906           0 :     if (Support::test(impl->options, JitAllocatorOptions::kFillUnusedMemory))
     907           0 :       JitAllocatorImpl_fillPattern(block->rwPtr() + (areaStart + areaShrunkSize) * pool->granularity, fillPattern(), areaDiff * pool->granularity);
     908             :   }
     909             : 
     910             :   return kErrorOk;
     911             : }
     912             : 
     913             : // JitAllocator - Tests
     914             : // ====================
     915             : 
     916             : #if defined(ASMJIT_TEST)
     917             : // A pseudo random number generator based on a paper by Sebastiano Vigna:
     918             : //   http://vigna.di.unimi.it/ftp/papers/xorshiftplus.pdf
     919             : class Random {
     920             : public:
     921             :   // Constants suggested as `23/18/5`.
     922             :   enum Steps : uint32_t {
     923             :     kStep1_SHL = 23,
     924             :     kStep2_SHR = 18,
     925             :     kStep3_SHR = 5
     926             :   };
     927             : 
     928             :   inline explicit Random(uint64_t seed = 0) noexcept { reset(seed); }
     929             :   inline Random(const Random& other) noexcept = default;
     930             : 
     931             :   inline void reset(uint64_t seed = 0) noexcept {
     932             :     // The number is arbitrary, it means nothing.
     933             :     constexpr uint64_t kZeroSeed = 0x1F0A2BE71D163FA0u;
     934             : 
     935             :     // Generate the state data by using splitmix64.
     936             :     for (uint32_t i = 0; i < 2; i++) {
     937             :       seed += 0x9E3779B97F4A7C15u;
     938             :       uint64_t x = seed;
     939             :       x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9u;
     940             :       x = (x ^ (x >> 27)) * 0x94D049BB133111EBu;
     941             :       x = (x ^ (x >> 31));
     942             :       _state[i] = x != 0 ? x : kZeroSeed;
     943             :     }
     944             :   }
     945             : 
     946             :   inline uint32_t nextUInt32() noexcept {
     947             :     return uint32_t(nextUInt64() >> 32);
     948             :   }
     949             : 
     950             :   inline uint64_t nextUInt64() noexcept {
     951             :     uint64_t x = _state[0];
     952             :     uint64_t y = _state[1];
     953             : 
     954             :     x ^= x << kStep1_SHL;
     955             :     y ^= y >> kStep3_SHR;
     956             :     x ^= x >> kStep2_SHR;
     957             :     x ^= y;
     958             : 
     959             :     _state[0] = y;
     960             :     _state[1] = x;
     961             :     return x + y;
     962             :   }
     963             : 
     964             :   uint64_t _state[2];
     965             : };
     966             : 
     967             : // Helper class to verify that JitAllocator doesn't return addresses that overlap.
     968             : class JitAllocatorWrapper {
     969             : public:
     970             :   // Address to a memory region of a given size.
     971             :   class Range {
     972             :   public:
     973             :     inline Range(uint8_t* addr, size_t size) noexcept
     974             :       : addr(addr),
     975             :         size(size) {}
     976             :     uint8_t* addr;
     977             :     size_t size;
     978             :   };
     979             : 
     980             :   // Based on JitAllocator::Block, serves our purpose well...
     981             :   class Record : public ZoneTreeNodeT<Record>,
     982             :                  public Range {
     983             :   public:
     984             :     inline Record(uint8_t* addr, size_t size)
     985             :       : ZoneTreeNodeT<Record>(),
     986             :         Range(addr, size) {}
     987             : 
     988             :     inline bool operator<(const Record& other) const noexcept { return addr < other.addr; }
     989             :     inline bool operator>(const Record& other) const noexcept { return addr > other.addr; }
     990             : 
     991             :     inline bool operator<(const uint8_t* key) const noexcept { return addr + size <= key; }
     992             :     inline bool operator>(const uint8_t* key) const noexcept { return addr > key; }
     993             :   };
     994             : 
     995             :   Zone _zone;
     996             :   ZoneAllocator _heap;
     997             :   ZoneTree<Record> _records;
     998             :   JitAllocator _allocator;
     999             : 
    1000             :   explicit JitAllocatorWrapper(const JitAllocator::CreateParams* params) noexcept
    1001             :     : _zone(1024 * 1024),
    1002             :       _heap(&_zone),
    1003             :       _allocator(params) {}
    1004             : 
    1005             :   void _insert(void* p_, size_t size) noexcept {
    1006             :     uint8_t* p = static_cast<uint8_t*>(p_);
    1007             :     uint8_t* pEnd = p + size - 1;
    1008             : 
    1009             :     Record* record;
    1010             : 
    1011             :     record = _records.get(p);
    1012             :     if (record)
    1013             :       EXPECT(record == nullptr, "Address [%p:%p] collides with a newly allocated [%p:%p]\n", record->addr, record->addr + record->size, p, p + size);
    1014             : 
    1015             :     record = _records.get(pEnd);
    1016             :     if (record)
    1017             :       EXPECT(record == nullptr, "Address [%p:%p] collides with a newly allocated [%p:%p]\n", record->addr, record->addr + record->size, p, p + size);
    1018             : 
    1019             :     record = _heap.newT<Record>(p, size);
    1020             :     EXPECT(record != nullptr, "Out of memory, cannot allocate 'Record'");
    1021             : 
    1022             :     _records.insert(record);
    1023             :   }
    1024             : 
    1025             :   void _remove(void* p) noexcept {
    1026             :     Record* record = _records.get(static_cast<uint8_t*>(p));
    1027             :     EXPECT(record != nullptr, "Address [%p] doesn't exist\n", p);
    1028             : 
    1029             :     _records.remove(record);
    1030             :     _heap.release(record, sizeof(Record));
    1031             :   }
    1032             : 
    1033             :   void* alloc(size_t size) noexcept {
    1034             :     void* rxPtr;
    1035             :     void* rwPtr;
    1036             : 
    1037             :     Error err = _allocator.alloc(&rxPtr, &rwPtr, size);
    1038             :     EXPECT(err == kErrorOk, "JitAllocator failed to allocate %zu bytes\n", size);
    1039             : 
    1040             :     _insert(rxPtr, size);
    1041             :     return rxPtr;
    1042             :   }
    1043             : 
    1044             :   void release(void* p) noexcept {
    1045             :     _remove(p);
    1046             :     EXPECT(_allocator.release(p) == kErrorOk, "JitAllocator failed to release '%p'\n", p);
    1047             :   }
    1048             : 
    1049             :   void shrink(void* p, size_t newSize) noexcept {
    1050             :     Record* record = _records.get(static_cast<uint8_t*>(p));
    1051             :     EXPECT(record != nullptr, "Address [%p] doesn't exist\n", p);
    1052             : 
    1053             :     if (!newSize)
    1054             :       return release(p);
    1055             : 
    1056             :     Error err = _allocator.shrink(p, newSize);
    1057             :     EXPECT(err == kErrorOk, "JitAllocator failed to shrink %p to %zu bytes\n", p, newSize);
    1058             : 
    1059             :     record->size = newSize;
    1060             :   }
    1061             : };
    1062             : 
    1063             : static void JitAllocatorTest_shuffle(void** ptrArray, size_t count, Random& prng) noexcept {
    1064             :   for (size_t i = 0; i < count; ++i)
    1065             :     std::swap(ptrArray[i], ptrArray[size_t(prng.nextUInt32() % count)]);
    1066             : }
    1067             : 
    1068             : static void JitAllocatorTest_usage(JitAllocator& allocator) noexcept {
    1069             :   JitAllocator::Statistics stats = allocator.statistics();
    1070             :   INFO("    Block Count       : %9llu [Blocks]"        , (unsigned long long)(stats.blockCount()));
    1071             :   INFO("    Reserved (VirtMem): %9llu [Bytes]"         , (unsigned long long)(stats.reservedSize()));
    1072             :   INFO("    Used     (VirtMem): %9llu [Bytes] (%.1f%%)", (unsigned long long)(stats.usedSize()), stats.usedSizeAsPercent());
    1073             :   INFO("    Overhead (HeapMem): %9llu [Bytes] (%.1f%%)", (unsigned long long)(stats.overheadSize()), stats.overheadSizeAsPercent());
    1074             : }
    1075             : 
    1076             : template<typename T, size_t kPatternSize, bool Bit>
    1077             : static void BitVectorRangeIterator_testRandom(Random& rnd, size_t count) noexcept {
    1078             :   for (size_t i = 0; i < count; i++) {
    1079             :     T in[kPatternSize];
    1080             :     T out[kPatternSize];
    1081             : 
    1082             :     for (size_t j = 0; j < kPatternSize; j++) {
    1083             :       in[j] = T(uint64_t(rnd.nextUInt32() & 0xFFu) * 0x0101010101010101);
    1084             :       out[j] = Bit == 0 ? Support::allOnes<T>() : T(0);
    1085             :     }
    1086             : 
    1087             :     {
    1088             :       BitVectorRangeIterator<T, Bit> it(in, kPatternSize);
    1089             :       size_t rangeStart, rangeEnd;
    1090             :       while (it.nextRange(&rangeStart, &rangeEnd)) {
    1091             :         if (Bit)
    1092             :           Support::bitVectorFill(out, rangeStart, rangeEnd - rangeStart);
    1093             :         else
    1094             :           Support::bitVectorClear(out, rangeStart, rangeEnd - rangeStart);
    1095             :       }
    1096             :     }
    1097             : 
    1098             :     for (size_t j = 0; j < kPatternSize; j++) {
    1099             :       EXPECT(in[j] == out[j], "Invalid pattern detected at [%zu] (%llX != %llX)", j, (unsigned long long)in[j], (unsigned long long)out[j]);
    1100             :     }
    1101             :   }
    1102             : }
    1103             : 
    1104             : UNIT(jit_allocator) {
    1105             :   size_t kCount = BrokenAPI::hasArg("--quick") ? 1000 : 100000;
    1106             : 
    1107             :   struct TestParams {
    1108             :     const char* name;
    1109             :     JitAllocatorOptions options;
    1110             :     uint32_t blockSize;
    1111             :     uint32_t granularity;
    1112             :   };
    1113             : 
    1114             :   static TestParams testParams[] = {
    1115             :     { "Default", JitAllocatorOptions::kNone, 0, 0 },
    1116             :     { "16MB blocks", JitAllocatorOptions::kNone, 16 * 1024 * 1024, 0 },
    1117             :     { "256B granularity", JitAllocatorOptions::kNone, 0, 256 },
    1118             :     { "kUseDualMapping", JitAllocatorOptions::kUseDualMapping, 0, 0 },
    1119             :     { "kUseMultiplePools", JitAllocatorOptions::kUseMultiplePools, 0, 0 },
    1120             :     { "kFillUnusedMemory", JitAllocatorOptions::kFillUnusedMemory, 0, 0 },
    1121             :     { "kImmediateRelease", JitAllocatorOptions::kImmediateRelease, 0, 0 },
    1122             :     { "kUseDualMapping | kFillUnusedMemory", JitAllocatorOptions::kUseDualMapping | JitAllocatorOptions::kFillUnusedMemory, 0, 0 }
    1123             :   };
    1124             : 
    1125             :   INFO("BitVectorRangeIterator<uint32_t>");
    1126             :   {
    1127             :     Random rnd;
    1128             :     BitVectorRangeIterator_testRandom<uint32_t, 64, 0>(rnd, kCount);
    1129             :   }
    1130             : 
    1131             :   INFO("BitVectorRangeIterator<uint64_t>");
    1132             :   {
    1133             :     Random rnd;
    1134             :     BitVectorRangeIterator_testRandom<uint64_t, 64, 0>(rnd, kCount);
    1135             :   }
    1136             : 
    1137             :   for (uint32_t testId = 0; testId < ASMJIT_ARRAY_SIZE(testParams); testId++) {
    1138             :     INFO("JitAllocator(%s)", testParams[testId].name);
    1139             : 
    1140             :     JitAllocator::CreateParams params {};
    1141             :     params.options = testParams[testId].options;
    1142             :     params.blockSize = testParams[testId].blockSize;
    1143             :     params.granularity = testParams[testId].granularity;
    1144             : 
    1145             :     size_t fixedBlockSize = 256;
    1146             : 
    1147             :     JitAllocatorWrapper wrapper(&params);
    1148             :     Random prng(100);
    1149             : 
    1150             :     size_t i;
    1151             : 
    1152             :     INFO("  Memory alloc/release test - %d allocations", kCount);
    1153             : 
    1154             :     void** ptrArray = (void**)::malloc(sizeof(void*) * size_t(kCount));
    1155             :     EXPECT(ptrArray != nullptr,
    1156             :           "Couldn't allocate '%u' bytes for pointer-array", unsigned(sizeof(void*) * size_t(kCount)));
    1157             : 
    1158             :     // Random blocks tests...
    1159             :     INFO("  Allocating random blocks...");
    1160             :     for (i = 0; i < kCount; i++)
    1161             :       ptrArray[i] = wrapper.alloc((prng.nextUInt32() % 1024) + 8);
    1162             :     JitAllocatorTest_usage(wrapper._allocator);
    1163             : 
    1164             :     INFO("  Releasing all allocated blocks from the beginning...");
    1165             :     for (i = 0; i < kCount; i++)
    1166             :       wrapper.release(ptrArray[i]);
    1167             :     JitAllocatorTest_usage(wrapper._allocator);
    1168             : 
    1169             :     INFO("  Allocating random blocks again...", kCount);
    1170             :     for (i = 0; i < kCount; i++)
    1171             :       ptrArray[i] = wrapper.alloc((prng.nextUInt32() % 1024) + 8);
    1172             :     JitAllocatorTest_usage(wrapper._allocator);
    1173             : 
    1174             :     INFO("  Shuffling allocated blocks...");
    1175             :     JitAllocatorTest_shuffle(ptrArray, unsigned(kCount), prng);
    1176             : 
    1177             :     INFO("  Releasing 50%% of allocated blocks...");
    1178             :     for (i = 0; i < kCount / 2; i++)
    1179             :       wrapper.release(ptrArray[i]);
    1180             :     JitAllocatorTest_usage(wrapper._allocator);
    1181             : 
    1182             :     INFO("  Allocating 50%% more blocks again...");
    1183             :     for (i = 0; i < kCount / 2; i++)
    1184             :       ptrArray[i] = wrapper.alloc((prng.nextUInt32() % 1024) + 8);
    1185             :     JitAllocatorTest_usage(wrapper._allocator);
    1186             : 
    1187             :     INFO("  Releasing all allocated blocks from the end...");
    1188             :     for (i = 0; i < kCount; i++)
    1189             :       wrapper.release(ptrArray[kCount - i - 1]);
    1190             :     JitAllocatorTest_usage(wrapper._allocator);
    1191             : 
    1192             :     // Fixed blocks tests...
    1193             :     INFO("  Allocating %zuB blocks...", fixedBlockSize);
    1194             :     for (i = 0; i < kCount / 2; i++)
    1195             :       ptrArray[i] = wrapper.alloc(fixedBlockSize);
    1196             :     JitAllocatorTest_usage(wrapper._allocator);
    1197             : 
    1198             :     INFO("  Shrinking each %zuB block to 1 byte", fixedBlockSize);
    1199             :     for (i = 0; i < kCount / 2; i++)
    1200             :       wrapper.shrink(ptrArray[i], 1);
    1201             :     JitAllocatorTest_usage(wrapper._allocator);
    1202             : 
    1203             :     INFO("  Allocating more 64B blocks...", 64);
    1204             :     for (i = kCount / 2; i < kCount; i++)
    1205             :       ptrArray[i] = wrapper.alloc(64);
    1206             :     JitAllocatorTest_usage(wrapper._allocator);
    1207             : 
    1208             :     INFO("  Releasing all blocks from the beginning...");
    1209             :     for (i = 0; i < kCount; i++)
    1210             :       wrapper.release(ptrArray[i]);
    1211             :     JitAllocatorTest_usage(wrapper._allocator);
    1212             : 
    1213             :     INFO("  Allocating %zuB blocks...", fixedBlockSize);
    1214             :     for (i = 0; i < kCount; i++)
    1215             :       ptrArray[i] = wrapper.alloc(fixedBlockSize);
    1216             :     JitAllocatorTest_usage(wrapper._allocator);
    1217             : 
    1218             :     INFO("  Shuffling allocated blocks...");
    1219             :     JitAllocatorTest_shuffle(ptrArray, unsigned(kCount), prng);
    1220             : 
    1221             :     INFO("  Releasing 50%% of allocated blocks...");
    1222             :     for (i = 0; i < kCount / 2; i++)
    1223             :       wrapper.release(ptrArray[i]);
    1224             :     JitAllocatorTest_usage(wrapper._allocator);
    1225             : 
    1226             :     INFO("  Allocating 50%% more %zuB blocks again...", fixedBlockSize);
    1227             :     for (i = 0; i < kCount / 2; i++)
    1228             :       ptrArray[i] = wrapper.alloc(fixedBlockSize);
    1229             :     JitAllocatorTest_usage(wrapper._allocator);
    1230             : 
    1231             :     INFO("  Releasing all allocated blocks from the end...");
    1232             :     for (i = 0; i < kCount; i++)
    1233             :       wrapper.release(ptrArray[kCount - i - 1]);
    1234             :     JitAllocatorTest_usage(wrapper._allocator);
    1235             : 
    1236             :     ::free(ptrArray);
    1237             :   }
    1238             : }
    1239             : #endif
    1240             : 
    1241             : ASMJIT_END_NAMESPACE
    1242             : 
    1243             : #endif
    1244             : 
    1245             : #endif // __PLUMED_HAS_ASMJIT

Generated by: LCOV version 1.16