aboutsummaryrefslogtreecommitdiff
path: root/src/dynarmic
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-31 00:27:24 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-31 00:27:24 -0400
commite9f31461b6ff98643bc2bfe330914d57b5facf38 (patch)
treeed121bcac4280784d488d86df193f6a15112f81a /src/dynarmic
parentb40ad217ab829226841cb71f6e4c5cb05977a225 (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/dynarmic')
-rw-r--r--src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp34
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