diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-31 00:27:24 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-31 00:27:24 -0400 |
| commit | e9f31461b6ff98643bc2bfe330914d57b5facf38 (patch) | |
| tree | ed121bcac4280784d488d86df193f6a15112f81a /src | |
| parent | b40ad217ab829226841cb71f6e4c5cb05977a225 (diff) | |
dynarmic: commit x64 code cache memory as code is emitted
Xbyak's default allocator commits the whole code cache up front: 512 MiB
per emulated core, 3 GiB of commit charge for six cores, of which a game
fills a small part (Mario Kart World: ~80-120 MiB per core). On Windows,
reserve the space instead and let EnsureMemoryCommitted, which already
runs before every emission, commit it in 4 MiB steps. Other platforms
keep the default allocator (their mappings commit on first touch).
Diffstat (limited to 'src')
| -rw-r--r-- | src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp | 34 |
1 files changed, 32 insertions, 2 deletions
diff --git a/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp b/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp index 384d6203a4..57001d4ef0 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp @@ -60,6 +60,34 @@ namespace { constexpr size_t CONSTANT_POOL_SIZE = 2 * 1024 * 1024; constexpr size_t PRELUDE_COMMIT_SIZE = 16 * 1024 * 1024; +#ifdef _WIN32 +constexpr size_t COMMIT_STEP = 4 * 1024 * 1024; + +// Reserves the code space; EnsureMemoryCommitted commits it as code is emitted. Xbyak's default +// allocator commits all of it up front: 512 MiB per core, 3 GiB of commit charge for six cores +// of which a game fills a fraction. +struct ReservingAllocator final : Xbyak::Allocator { + uint8_t* alloc(size_t size) override { + return static_cast<uint8_t*>(VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_NOACCESS)); + } + void free(uint8_t* p) override { + VirtualFree(p, 0, MEM_RELEASE); + } + bool useProtect() const override { + return false; + } +}; + +Xbyak::Allocator* CodeAllocator() { + // Never destroyed: code blocks may outlive static destruction. + static auto* const allocator = new ReservingAllocator; + return allocator; +} +#else +Xbyak::Allocator* CodeAllocator() { + return nullptr; +} +#endif #ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT void ProtectMemory(const void* base, size_t size, bool is_executable) { @@ -149,7 +177,7 @@ BlockOfCode::BlockOfCode(RunCodeCallbacks cb, JitStateInfo jsi, size_t total_cod #else , nullptr //Allow RWE #endif - , nullptr) + , CodeAllocator()) , constant_pool(*this, CONSTANT_POOL_SIZE) , jsi(jsi) , cb(std::move(cb)) @@ -206,7 +234,9 @@ size_t BlockOfCode::SpaceRemaining() const { void BlockOfCode::EnsureMemoryCommitted([[maybe_unused]] size_t codesize) { #ifdef _WIN32 if (committed_size < size_ + codesize) { - committed_size = std::min<size_t>(maxSize_, committed_size + codesize); + // The code space is only reserved (CodeAllocator), so everything up to the end of the next + // emission has to be committed; in steps, as each VirtualAlloc call is a kernel call. + committed_size = std::min<size_t>(maxSize_, (std::max)(committed_size + COMMIT_STEP, size_ + codesize)); # ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT VirtualAlloc(top_, committed_size, MEM_COMMIT, PAGE_READWRITE); # else |
