mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 20:00:16 +02:00
The various places that were using the CodeBuffer object were using internal implementation details that are changing as we move over to a bitmap allocator. Preempt this by hiding some of the implementation details early without changing behaviour. `GetBufferBase` is still technically leaking some of the internal details, but it needs changes around how relocations are being handled and how the disk cache validation works in order to handle that right now. Should be no functional change.
131 lines
4.0 KiB
C++
131 lines
4.0 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 num of bytes currently allocated from the allocator.
|
|
size_t GetAllocatedSize() 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
|