Skip to content

Commit fff8304

Browse files
committed
Jit: Use LazyMemoryRegion-backed bit sets
JitCache has been using a class called ValidBlockBitSet. This commit replaces it with a new class called LazyMemoryRegionBitSet, which works similarly but is backed by a LazyMemoryRegion, letting us skip allocating memory for parts of the bit set that correspond to emulated memory regions where the game isn't storing any code. I have also added optimized functions for setting or clearing long runs of bits. Additionally, this commit replaces JitBase's three std::unordered_sets that are keeping track of instructions with LazyMemoryRegionBitSets. Here the benefit is more speed than memory usage. This improves NBA Live 2005's menu performance by 1% or so in SuperSamus's testing.
1 parent cd3ea81 commit fff8304

28 files changed

Lines changed: 422 additions & 95 deletions

Source/Core/Common/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ add_library(common
9999
JsonUtil.h
100100
JsonUtil.cpp
101101
Lazy.h
102+
LazyMemoryRegionBitSet.cpp
103+
LazyMemoryRegionBitSet.h
102104
LinearDiskCache.h
103105
UnixUtil.h
104106
Logging/ConsoleListener.h
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright 2026 Dolphin Emulator Project
2+
// SPDX-License-Identifier: GPL-2.0-or-later
3+
4+
#include "Common/LazyMemoryRegionBitSet.h"
5+
6+
#include <cstring>
7+
8+
#include "Common/MsgHandler.h"
9+
10+
namespace Common
11+
{
12+
LazyMemoryRegionBitSet::LazyMemoryRegionBitSet(size_t bits) : m_bits(bits), m_valid(Create(bits))
13+
{
14+
}
15+
16+
bool LazyMemoryRegionBitSet::Create(size_t bits)
17+
{
18+
const size_t page_size = MemArena().GetPageSize();
19+
if (bits % (page_size * 8) != 0)
20+
{
21+
PanicAlertFmt(
22+
"LazyMemoryRegionBitSet size of {} bits is not aligned with page size of {} bytes", bits,
23+
page_size);
24+
return false;
25+
}
26+
27+
m_pointer = reinterpret_cast<u32*>(m_region.Create(bits / 8));
28+
if (!m_pointer)
29+
{
30+
PanicAlertFmt("LazyMemoryRegionBitSet failed to create backing region");
31+
return false;
32+
}
33+
34+
return true;
35+
}
36+
37+
void LazyMemoryRegionBitSet::Clear()
38+
{
39+
m_region.Clear();
40+
}
41+
42+
void LazyMemoryRegionBitSet::SetBits(size_t bits_start, size_t bits_end)
43+
{
44+
if (bits_start == bits_end)
45+
return;
46+
47+
RangeCheck(bits_end);
48+
49+
m_region.EnsureMemoryPagesWritable(bits_start / 8, (bits_end - bits_start) / 8);
50+
51+
const u32 extra_first_word = 0xFFFFFFFF << bits_start % 32;
52+
const u32 extra_last_word = ~(0xFFFFFFFF << bits_end % 32);
53+
54+
if (bits_start / 32 == bits_end / 32)
55+
{
56+
m_pointer[bits_start / 32] |= extra_first_word & extra_last_word;
57+
return;
58+
}
59+
60+
if (bits_start % 32 != 0)
61+
m_pointer[bits_start / 32] |= extra_first_word;
62+
63+
const size_t start_index = (bits_start + 31) / 32;
64+
const size_t end_index = bits_end / 32;
65+
std::memset(m_pointer + start_index, 0xFF, (end_index - start_index) * 4);
66+
67+
if (bits_end % 32 != 0)
68+
m_pointer[bits_end / 32] |= extra_last_word;
69+
}
70+
71+
void LazyMemoryRegionBitSet::ClearBits(size_t bits_start, size_t bits_end)
72+
{
73+
if (bits_start == bits_end)
74+
return;
75+
76+
RangeCheck(bits_end);
77+
78+
m_region.EnsureMemoryPagesWritable(bits_start / 8, (bits_end - bits_start) / 8);
79+
80+
const u32 extra_first_word = ~(0xFFFFFFFF << bits_start % 32);
81+
const u32 extra_last_word = 0xFFFFFFFF << bits_end % 32;
82+
83+
if (bits_start / 32 == bits_end / 32)
84+
{
85+
m_pointer[bits_start / 32] &= extra_first_word | extra_last_word;
86+
return;
87+
}
88+
89+
if (bits_start % 32 != 0)
90+
m_pointer[bits_start / 32] &= extra_first_word;
91+
92+
const size_t start_index = (bits_start + 31) / 32;
93+
const size_t end_index = bits_end / 32;
94+
std::memset(m_pointer + start_index, 0x00, (end_index - start_index) * 4);
95+
96+
if (bits_end % 32 != 0)
97+
m_pointer[bits_end / 32] &= extra_last_word;
98+
}
99+
100+
} // namespace Common
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2026 Dolphin Emulator Project
2+
// SPDX-License-Identifier: GPL-2.0-or-later
3+
4+
#pragma once
5+
6+
#include <cstddef>
7+
8+
#include "Common/Assert.h"
9+
#include "Common/MemArena.h"
10+
11+
namespace Common
12+
{
13+
// A bit set backed by a LazyMemoryRegion.
14+
class LazyMemoryRegionBitSet final
15+
{
16+
public:
17+
// Allocates backing memory and creates a bit set.
18+
// Allocating backing memory may fail; use IsValid to check for success.
19+
LazyMemoryRegionBitSet(size_t bits);
20+
21+
bool IsValid() const { return m_valid; }
22+
23+
void Clear();
24+
25+
bool IsBitSet(size_t bit) const
26+
{
27+
RangeCheck(bit);
28+
return (m_pointer[bit / 32] & (1 << (bit % 32))) != 0;
29+
}
30+
31+
void SetBit(size_t bit)
32+
{
33+
RangeCheck(bit);
34+
m_region.EnsureMemoryPageWritable(bit / 8);
35+
m_pointer[bit / 32] |= (1 << (bit % 32));
36+
}
37+
38+
void ClearBit(size_t bit)
39+
{
40+
RangeCheck(bit);
41+
m_region.EnsureMemoryPageWritable(bit / 8);
42+
m_pointer[bit / 32] &= ~(1 << (bit % 32));
43+
}
44+
45+
void SetBits(size_t bits_start, size_t bits_end);
46+
47+
void ClearBits(size_t bits_start, size_t bits_end);
48+
49+
void RangeCheck(size_t bit) const
50+
{
51+
DEBUG_ASSERT_MSG(COMMON, m_valid, "LazyMemoryRegionBitSet isn't valid");
52+
DEBUG_ASSERT_MSG(COMMON, bit < m_bits,
53+
"Tried to access bit {} in a LazyMemoryRegionBitSet with only {} bits", bit,
54+
m_bits);
55+
}
56+
57+
const u32* GetRawMemory() const { return m_pointer; }
58+
59+
private:
60+
LazyMemoryRegion m_region;
61+
u32* m_pointer;
62+
size_t m_bits;
63+
bool m_valid;
64+
65+
bool Create(size_t bits);
66+
};
67+
68+
} // namespace Common

Source/Core/Core/FifoPlayer/FifoPlayer.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ class FifoPlayer::CPUCore final : public CPUCoreBase
217217
~CPUCore() override {}
218218
CPUCore& operator=(const CPUCore&) = delete;
219219

220-
void Init() override
220+
bool Init() override
221221
{
222222
IsPlayingBackFifologWithBrokenEFBCopies = m_parent->m_File->HasBrokenEFBCopies();
223223
// Without this call, we deadlock in initialization in dual core, as the FIFO is disabled and
@@ -226,6 +226,8 @@ class FifoPlayer::CPUCore final : public CPUCoreBase
226226

227227
m_parent->m_CurrentFrame = m_parent->m_FrameRangeStart;
228228
m_parent->LoadMemory();
229+
230+
return true;
229231
}
230232

231233
void Shutdown() override { IsPlayingBackFifologWithBrokenEFBCopies = false; }

Source/Core/Core/PowerPC/CPUCoreBase.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class CPUCoreBase
77
{
88
public:
99
virtual ~CPUCoreBase() = default;
10-
virtual void Init() = 0;
10+
[[nodiscard]] virtual bool Init() = 0;
1111
virtual void Shutdown() = 0;
1212
virtual void ClearCache() = 0;
1313
virtual void Run() = 0;

Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,26 @@ CachedInterpreter::CachedInterpreter(Core::System& system) : JitBase(system), m_
3131

3232
CachedInterpreter::~CachedInterpreter() = default;
3333

34-
void CachedInterpreter::Init()
34+
bool CachedInterpreter::Init()
3535
{
36+
if (!CheckValidity())
37+
return false;
38+
3639
RefreshConfig();
3740

3841
AllocCodeSpace(CODE_SIZE);
3942
ResetFreeMemoryRanges();
4043

4144
jo.enableBlocklink = false;
4245

43-
m_block_cache.Init();
46+
if (!m_block_cache.Init())
47+
return false;
4448

4549
code_block.m_stats = &js.st;
4650
code_block.m_gpa = &js.gpa;
4751
code_block.m_fpa = &js.fpa;
52+
53+
return true;
4854
}
4955

5056
void CachedInterpreter::Shutdown()

Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class CachedInterpreter : public JitBase, public CachedInterpreterCodeBlock
3232
CachedInterpreter& operator=(CachedInterpreter&&) = delete;
3333
~CachedInterpreter() override;
3434

35-
void Init() override;
35+
[[nodiscard]] bool Init() override;
3636
void Shutdown() override;
3737

3838
bool HandleFault(uintptr_t access_address, SContext* ctx) override { return false; }

Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreterBlockCache.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ CachedInterpreterBlockCache::CachedInterpreterBlockCache(JitBase& jit) : JitBase
1010
{
1111
}
1212

13-
void CachedInterpreterBlockCache::Init()
13+
bool CachedInterpreterBlockCache::Init()
1414
{
15-
JitBaseBlockCache::Init();
1615
ClearRangesToFree();
16+
return JitBaseBlockCache::Init();
1717
}
1818

1919
void CachedInterpreterBlockCache::DestroyBlock(JitBlock& block)

Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreterBlockCache.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class CachedInterpreterBlockCache final : public JitBaseBlockCache
1616
public:
1717
explicit CachedInterpreterBlockCache(JitBase& jit);
1818

19-
void Init() override;
19+
[[nodiscard]] bool Init() override;
2020

2121
void DestroyBlock(JitBlock& block) override;
2222

Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,11 @@ Interpreter::Interpreter(Core::System& system, PowerPC::PowerPCState& ppc_state,
7070

7171
Interpreter::~Interpreter() = default;
7272

73-
void Interpreter::Init()
73+
bool Interpreter::Init()
7474
{
7575
m_end_block = false;
76+
77+
return true;
7678
}
7779

7880
void Interpreter::Shutdown()

0 commit comments

Comments
 (0)