mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-10-20 17:48:59 +02:00
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.
163 lines
4.6 KiB
C++
163 lines
4.6 KiB
C++
// Copyright 2026 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include <fmt/format.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "Common/LazyMemoryRegionBitSet.h"
|
|
#include "Common/MemArena.h"
|
|
|
|
// Intentionally not divisible by 8
|
|
static constexpr size_t BIT_COUNT = 1024 + 512 + 1;
|
|
|
|
// Two blocks for the test to write to, and one block that remains mapped to the default page so we
|
|
// can check that we didn't accidentally write to the default page
|
|
static constexpr size_t WINDOWS_BOUNDARY_BIT_COUNT =
|
|
Common::LazyMemoryRegion::WINDOWS_BLOCK_SIZE * 8 * 3;
|
|
|
|
static u32 ExpectedContentAllZeroes(size_t)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static u32 ExpectedContentAllOnes(size_t)
|
|
{
|
|
return 0xFFFFFFFF;
|
|
}
|
|
|
|
static u32 ExpectedContentAfterSetting(size_t index)
|
|
{
|
|
switch (index)
|
|
{
|
|
case 32:
|
|
return 0x0000FF00;
|
|
case 34:
|
|
return 0x0FFFFFF0;
|
|
case 35:
|
|
return 0xFFFFFFF0;
|
|
case 36:
|
|
return 0x0FFFFFFF;
|
|
case 37:
|
|
return 0xFFFFFFF0;
|
|
case 39:
|
|
return 0x0FFFFFFF;
|
|
case 40:
|
|
return 0xFFFFFFF0;
|
|
case 47:
|
|
return 0x0FFFFFFF;
|
|
default:
|
|
if (index < 32)
|
|
return 1 << index;
|
|
if (index < 48)
|
|
return 0xFFFFFFFF;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static u32 ExpectedContentAfterClearing(size_t index)
|
|
{
|
|
return ~ExpectedContentAfterSetting(index);
|
|
}
|
|
|
|
static u32 ExpectedContentForWindowsBoundary(size_t index)
|
|
{
|
|
switch (index)
|
|
{
|
|
case Common::LazyMemoryRegion::WINDOWS_BLOCK_SIZE / sizeof(u32) - 1:
|
|
return 0x80000000;
|
|
case Common::LazyMemoryRegion::WINDOWS_BLOCK_SIZE / sizeof(u32):
|
|
return 0x00000001;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
template <typename F>
|
|
static void VerifyContent(const u32* memory, size_t bits, F f)
|
|
{
|
|
const size_t end_index = bits / 32;
|
|
for (size_t i = 0; i < end_index; ++i, ++memory)
|
|
{
|
|
const u32 expected = f(i);
|
|
ASSERT_EQ(*memory, expected) << "i=" << i;
|
|
}
|
|
if (bits % 32 != 0)
|
|
{
|
|
const u32 expected = f(end_index) & ~(0xFFFFFFFF << bits % 32);
|
|
ASSERT_EQ(*memory, expected) << "i=" << end_index;
|
|
}
|
|
}
|
|
|
|
TEST(LazyMemoryRegionBitSet, Unwritten)
|
|
{
|
|
Common::LazyMemoryRegionBitSet bit_set(BIT_COUNT);
|
|
VerifyContent(bit_set.GetRawMemory(), BIT_COUNT, ExpectedContentAllZeroes);
|
|
}
|
|
|
|
TEST(LazyMemoryRegionBitSet, Set)
|
|
{
|
|
Common::LazyMemoryRegionBitSet bit_set(BIT_COUNT);
|
|
|
|
// Set single bits in the range 0-1023
|
|
for (size_t i = 0; i < 32; ++i)
|
|
bit_set.SetBit(i * 33);
|
|
|
|
// Set ranges of bits in bits 1024 and higher
|
|
bit_set.SetBits(1024 + 8, 1024 + 16);
|
|
bit_set.SetBits(1024 + 32, 1024 + 64);
|
|
bit_set.SetBits(1024 + 68, 1024 + 92);
|
|
bit_set.SetBits(1024 + 100, 1024 + 128);
|
|
bit_set.SetBits(1024 + 128, 1024 + 156);
|
|
bit_set.SetBits(1024 + 164, 1024 + 252);
|
|
bit_set.SetBits(1024 + 260, 1024 + 384);
|
|
bit_set.SetBits(1024 + 384, 1024 + 508);
|
|
|
|
VerifyContent(bit_set.GetRawMemory(), BIT_COUNT, ExpectedContentAfterSetting);
|
|
|
|
bit_set.Clear();
|
|
VerifyContent(bit_set.GetRawMemory(), BIT_COUNT, ExpectedContentAllZeroes);
|
|
}
|
|
|
|
TEST(LazyMemoryRegionBitSet, Clear)
|
|
{
|
|
Common::LazyMemoryRegionBitSet bit_set(BIT_COUNT);
|
|
bit_set.SetBits(0, BIT_COUNT);
|
|
VerifyContent(bit_set.GetRawMemory(), BIT_COUNT, ExpectedContentAllOnes);
|
|
|
|
// Clear single bits in the range 0-1023
|
|
for (size_t i = 0; i < 32; ++i)
|
|
bit_set.ClearBit(i * 33);
|
|
|
|
// Clear ranges of bits in bits 1024 and higher
|
|
bit_set.ClearBits(1024 + 8, 1024 + 16);
|
|
bit_set.ClearBits(1024 + 32, 1024 + 64);
|
|
bit_set.ClearBits(1024 + 68, 1024 + 92);
|
|
bit_set.ClearBits(1024 + 100, 1024 + 128);
|
|
bit_set.ClearBits(1024 + 128, 1024 + 156);
|
|
bit_set.ClearBits(1024 + 164, 1024 + 252);
|
|
bit_set.ClearBits(1024 + 260, 1024 + 384);
|
|
bit_set.ClearBits(1024 + 384, 1024 + 508);
|
|
|
|
VerifyContent(bit_set.GetRawMemory(), BIT_COUNT, ExpectedContentAfterClearing);
|
|
|
|
bit_set.Clear();
|
|
VerifyContent(bit_set.GetRawMemory(), BIT_COUNT, ExpectedContentAllZeroes);
|
|
}
|
|
|
|
TEST(LazyMemoryRegionBitSet, WindowsBoundary)
|
|
{
|
|
// Unlike on other operating systems, LazyMemoryRegion's Windows implementation allocates one page
|
|
// containing only zeroes and maps it to every part of the region by default. If
|
|
// LazyMemoryRegionBitSet wants to write to any part of the region, it first needs to call
|
|
// LazyMemoryRegion::EnsureMemoryPageWritable. This test is checking that
|
|
// LazyMemoryRegion::EnsureMemoryPageWritable is being called with the correct parameters.
|
|
|
|
Common::LazyMemoryRegionBitSet bit_set(WINDOWS_BOUNDARY_BIT_COUNT);
|
|
|
|
bit_set.SetBits(Common::LazyMemoryRegion::WINDOWS_BLOCK_SIZE * 8 - 1,
|
|
Common::LazyMemoryRegion::WINDOWS_BLOCK_SIZE * 8 + 1);
|
|
|
|
VerifyContent(bit_set.GetRawMemory(), WINDOWS_BOUNDARY_BIT_COUNT,
|
|
ExpectedContentForWindowsBoundary);
|
|
}
|