Files
FEX-Emu--FEX/FEXCore/Source/Interface/Core/SharedCodeBufferManager.h
T
Ryan Houdek db3817a260 FEXCore: Fixes ever shrinking JIT code buffer
I accidentally replaced a couple usages of `AllocatedSize` with
`GetAllocatedSize()`. This resulted in a JIT buffer that ran out of
space would actually allocate a slightly smaller buffer each time, and
then it cascades downwards resulting in catastrophic performance.

Fix the use in `SharedCodeBufferManager.cpp` and `Core.cpp` which were
incorrect and renames the function to be more explicit.
2026-08-22 18:31:22 -07:00

136 lines
4.2 KiB
C++

// SPDX-License-Identifier: MIT
/*
$info$
category: code buffer ~ Thread shared code buffer management
tags: backend|shared
$end_info$
*/
#pragma once
#include <FEXCore/fextl/memory.h>
#include <FEXCore/Utils/MathUtils.h>
#include <FEXCore/Utils/SignalScopeGuards.h>
#include <FEXCore/Utils/TypeDefines.h>
#include <cstddef>
#include <cstdint>
namespace FEXCore {
struct GuestToHostMap;
}
namespace FEXCore::CPU {
struct CodeBuffer {
fextl::unique_ptr<GuestToHostMap> 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<uintptr_t>(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<uint8_t*> 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<CodeBuffer> 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<CodeBuffer> StartLargerCodeBuffer();
// Allocate a new CodeBuffer with maximum internal size.
// Subsequent calls to GetLatest will point to the returned buffer.
fextl::shared_ptr<CodeBuffer> StartMaximalCodeBuffer();
virtual void OnCodeBufferAllocated(const std::shared_ptr<CodeBuffer>&) {};
private:
fextl::shared_ptr<CodeBuffer> Latest;
fextl::shared_ptr<CodeBuffer> AllocateNew(size_t Size);
};
} // namespace FEXCore::CPU