// SPDX-License-Identifier: MIT /* $info$ category: code buffer ~ Thread shared code buffer management tags: backend|shared $end_info$ */ #pragma once #include #include #include #include #include #include namespace FEXCore { struct GuestToHostMap; } namespace FEXCore::CPU { struct CodeBuffer { fextl::unique_ptr LookupCache; CodeBuffer(size_t Size); CodeBuffer(const CodeBuffer&) = delete; CodeBuffer& operator=(const CodeBuffer&) = delete; CodeBuffer(CodeBuffer&& oth) = delete; CodeBuffer& operator=(CodeBuffer&&) = delete; ~CodeBuffer(); // Atomically allocate a fixed size buffer out of the current allocated codebuffer. // Lockless because it's just a linear allocator. struct CodeBufferAllocation { const uint8_t* BufferBase; uint8_t* BufferAllocationOffset; }; CodeBufferAllocation AtomicAllocateBuffer(size_t Size) { Size = FEXCore::AlignUp(Size, 16); LOGMAN_THROW_A_FMT(reinterpret_cast(CodeBufferOffset.load()) % 16 == 0, "Buffer needs to always be 16B aligned!"); auto ExpectedOffset = CodeBufferOffset.load(std::memory_order_relaxed); auto DesiredOffset = ExpectedOffset + Size; if (DesiredOffset > CodeBufferEnd) { // Couldn't fit. return {}; } while (!CodeBufferOffset.compare_exchange_strong(ExpectedOffset, DesiredOffset)) { DesiredOffset = ExpectedOffset + Size; if (DesiredOffset > CodeBufferEnd) { // Couldn't fit. return {}; } } // Managed to fit. return { .BufferBase = Ptr, .BufferAllocationOffset = ExpectedOffset, }; } // Returns the total number of bytes available for storing code size_t UsableSize() const { return AllocatedSize - FEXCore::Utils::FEX_PAGE_SIZE; } // Returns the full size of the buffer, including the guard page. size_t TotalAllocationSize() const { return AllocatedSize; } // Returns the num of bytes currently allocated from the allocator. size_t AllocatedSpaceUsed() const { return CodeBufferOffset - Ptr; } // Trivially reset the allocator. void Reset() { CodeBufferOffset = Ptr; } // Returns the base of the buffer. uint8_t* GetBufferBase() const { return Ptr; } private: uint8_t* Ptr; uint8_t* CodeBufferEnd; size_t AllocatedSize; // including guard page; see UsableSize() // Code buffer allocation information. std::atomic CodeBufferOffset {}; }; /** * A manager that coordinates access to the CodeBuffer used for compiling new code across threads. * * The CodeBuffer is managed as a partially persistent data structure: * - Exactly one CodeBuffer is now designated as "active", which means data can be appended to it * - Lossy modifications to the active CodeBuffer will not invalidate any data in use by other threads (which is what enables save CodeBuffer sharing across threads) * - Instead, such lossy modifications trigger a new "version" of the data in the modifying thread. Old versions of the CodeBuffer persist as read-only data for use by the other threads. * - The other threads can update their version of the CodeBuffer. This will decrease the reference count and eventually trigger deallocation of the old version */ class SharedCodeBufferManager { public: virtual ~SharedCodeBufferManager() = default; // Get the CodeBuffer that was most recently allocated. // This is the only CodeBuffer that data may be written to. fextl::shared_ptr GetLatest(); // Allocate a new CodeBuffer with geometric growth up to an internal maximum. // Subsequent calls to GetLatest will point to the returned buffer. fextl::shared_ptr StartLargerCodeBuffer(); // Allocate a new CodeBuffer with maximum internal size. // Subsequent calls to GetLatest will point to the returned buffer. fextl::shared_ptr StartMaximalCodeBuffer(); virtual void OnCodeBufferAllocated(const std::shared_ptr&) {}; private: fextl::shared_ptr Latest; fextl::shared_ptr AllocateNew(size_t Size); }; } // namespace FEXCore::CPU