1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
// 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);
}
|