aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Memory/WindowsShared/PlaceholderList.cs
blob: 848e410e8ec656609f8567263ba00a4ec4145768 (plain) (blame)
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
using Ryujinx.Memory.Range;
using System;
using System.Diagnostics;

namespace Ryujinx.Memory.WindowsShared
{
    /// <summary>
    /// A specialized list used for keeping track of Windows 10's memory placeholders.
    /// This is used to make splitting a large placeholder into equally small
    /// granular chunks much easier, while avoiding slowdown due to a large number of
    /// placeholders by coalescing adjacent granular placeholders after they are unused.
    /// </summary>
    class PlaceholderList
    {
        private class PlaceholderBlock : IRange
        {
            public ulong Address { get; }
            public ulong Size { get; private set; }
            public ulong EndAddress { get; private set; }
            public bool IsGranular { get; set; }

            public PlaceholderBlock(ulong id, ulong size, bool isGranular)
            {
                Address = id;
                Size = size;
                EndAddress = id + size;
                IsGranular = isGranular;
            }

            public bool OverlapsWith(ulong address, ulong size)
            {
                return Address < address + size && address < EndAddress;
            }

            public void ExtendTo(ulong end, RangeList<PlaceholderBlock> list)
            {
                EndAddress = end;
                Size = end - Address;

                list.UpdateEndAddress(this);
            }
        }

        private RangeList<PlaceholderBlock> _placeholders;
        private PlaceholderBlock[] _foundBlocks = new PlaceholderBlock[32];

        /// <summary>
        /// Create a new list to manage placeholders.
        /// Note that a size is measured in granular placeholders.
        /// If the placeholder granularity is 65536 bytes, then a 65536 region will be covered by 1 placeholder granularity.
        /// </summary>
        /// <param name="size">Size measured in granular placeholders</param>
        public PlaceholderList(ulong size)
        {
            _placeholders = new RangeList<PlaceholderBlock>();

            _placeholders.Add(new PlaceholderBlock(0, size, false));
        }

        /// <summary>
        /// Ensure that the given range of placeholders is granular.
        /// </summary>
        /// <param name="id">Start of the range, measured in granular placeholders</param>
        /// <param name="size">Size of the range, measured in granular placeholders</param>
        /// <param name="splitPlaceholderCallback">Callback function to run when splitting placeholders, calls with (start, middle)</param>
        public void EnsurePlaceholders(ulong id, ulong size, Action<ulong, ulong> splitPlaceholderCallback)
        {
            // Search 1 before and after the placeholders, as we may need to expand/join granular regions surrounding the requested area.

            ulong endId = id + size;
            ulong searchStartId = id == 0 ? 0 : (id - 1);
            int blockCount = _placeholders.FindOverlapsNonOverlapping(searchStartId, (endId - searchStartId) + 1, ref _foundBlocks);

            PlaceholderBlock first = _foundBlocks[0];
            PlaceholderBlock last = _foundBlocks[blockCount - 1];
            bool overlapStart = first.EndAddress >= id && id != 0;
            bool overlapEnd = last.Address <= endId;

            for (int i = 0; i < blockCount; i++)
            {
                // Go through all non-granular blocks in the range and create placeholders.
                PlaceholderBlock block = _foundBlocks[i];

                if (block.Address <= id && block.EndAddress >= endId && block.IsGranular)
                {
                    return; // The region we're searching for is already granular.
                }

                if (!block.IsGranular)
                {
                    ulong placeholderStart = Math.Max(block.Address, id);
                    ulong placeholderEnd = Math.Min(block.EndAddress - 1, endId);

                    if (placeholderStart != block.Address && placeholderStart != block.EndAddress)
                    {
                        splitPlaceholderCallback(block.Address, placeholderStart - block.Address);
                    }

                    for (ulong j = placeholderStart; j < placeholderEnd; j++)
                    {
                        splitPlaceholderCallback(j, 1);
                    }
                }

                if (!((block == first && overlapStart) || (block == last && overlapEnd)))
                {
                    // Remove blocks that will be replaced
                    _placeholders.Remove(block);
                }
            }

            if (overlapEnd)
            {
                if (!(first == last && overlapStart))
                {
                    _placeholders.Remove(last);
                }

                if (last.IsGranular)
                {
                    endId = last.EndAddress;
                }
                else if (last.EndAddress != endId)
                {
                    _placeholders.Add(new PlaceholderBlock(endId, last.EndAddress - endId, false));
                }
            }

            if (overlapStart && first.IsGranular)
            {
                first.ExtendTo(endId, _placeholders);
            }
            else
            {
                if (overlapStart)
                {
                    first.ExtendTo(id, _placeholders);
                }

                _placeholders.Add(new PlaceholderBlock(id, endId - id, true));
            }

            ValidateList();
        }

        /// <summary>
        /// Coalesces placeholders in a given region, as they are not being used.
        /// This assumes that the region only contains placeholders - all views and allocations must have been replaced with placeholders.
        /// </summary>
        /// <param name="id">Start of the range, measured in granular placeholders</param>
        /// <param name="size">Size of the range, measured in granular placeholders</param>
        /// <param name="coalescePlaceholderCallback">Callback function to run when coalescing two placeholders, calls with (start, end)</param>
        public void RemovePlaceholders(ulong id, ulong size, Action<ulong, ulong> coalescePlaceholderCallback)
        {
            ulong endId = id + size;
            int blockCount = _placeholders.FindOverlapsNonOverlapping(id, size, ref _foundBlocks);

            PlaceholderBlock first = _foundBlocks[0];
            PlaceholderBlock last = _foundBlocks[blockCount - 1];

            // All granular blocks must have non-granular blocks surrounding them, unless they start at 0.
            // We must extend the non-granular blocks into the granular ones. This does mean that we need to search twice.

            if (first.IsGranular || last.IsGranular)
            {
                ulong surroundStart = Math.Max(0, (first.IsGranular && first.Address != 0) ? first.Address - 1 : id);
                blockCount = _placeholders.FindOverlapsNonOverlapping(
                    surroundStart,
                    (last.IsGranular ? last.EndAddress + 1 : endId) - surroundStart,
                    ref _foundBlocks);

                first = _foundBlocks[0];
                last = _foundBlocks[blockCount - 1];
            }

            if (first == last)
            {
                return; // Already coalesced.
            }

            PlaceholderBlock extendBlock = id == 0 ? null : first;
            bool newBlock = false;
            for (int i = extendBlock == null ? 0 : 1; i < blockCount; i++)
            {
                // Go through all granular blocks in the range and extend placeholders.
                PlaceholderBlock block = _foundBlocks[i];

                ulong blockEnd = block.EndAddress;
                ulong extendFrom;
                ulong extent = Math.Min(blockEnd, endId);

                if (block.Address < id && blockEnd > id)
                {
                    block.ExtendTo(id, _placeholders);
                    extendBlock = null;
                }
                else
                {
                    _placeholders.Remove(block);
                }

                if (extendBlock == null)
                {
                    extendFrom = id;
                    extendBlock = new PlaceholderBlock(id, extent - id, false);
                    _placeholders.Add(extendBlock);

                    if (blockEnd > extent)
                    {
                        _placeholders.Add(new PlaceholderBlock(extent, blockEnd - extent, true));

                        // Skip the next non-granular block, and extend from that into the granular block afterwards.
                        // (assuming that one is still in the requested range)

                        if (i + 1 < blockCount)
                        {
                            extendBlock = _foundBlocks[i + 1];
                        }

                        i++;
                    }

                    newBlock = true;
                }
                else
                {
                    extendFrom = extendBlock.Address;
                    extendBlock.ExtendTo(block.IsGranular ? extent : block.EndAddress, _placeholders);
                }

                if (block.IsGranular)
                {
                    ulong placeholderStart = Math.Max(block.Address, id);
                    ulong placeholderEnd = extent;

                    if (newBlock)
                    {
                        placeholderStart++;
                        newBlock = false;
                    }

                    for (ulong j = placeholderStart; j < placeholderEnd; j++)
                    {
                        coalescePlaceholderCallback(extendFrom, (j + 1) - extendFrom);
                    }

                    if (extent < block.EndAddress)
                    {
                        _placeholders.Add(new PlaceholderBlock(placeholderEnd, block.EndAddress - placeholderEnd, true));
                        ValidateList();
                        return;
                    }
                }
                else
                {
                    coalescePlaceholderCallback(extendFrom, block.EndAddress - extendFrom);
                }
            }

            ValidateList();
        }

        /// <summary>
        /// Ensure that the placeholder list is valid.
        /// A valid list should not have any gaps between the placeholders,
        /// and there may be no placehonders with the same IsGranular value next to each other.
        /// </summary>
        [Conditional("DEBUG")]
        private void ValidateList()
        {
            bool isGranular = false;
            bool first = true;
            ulong lastAddress = 0;

            foreach (var placeholder in _placeholders)
            {
                if (placeholder.Address != lastAddress)
                {
                    throw new InvalidOperationException("Gap in placeholder list.");
                }

                if (isGranular == placeholder.IsGranular && !first)
                {
                    throw new InvalidOperationException("Placeholder list not alternating.");
                }

                first = false;
                isGranular = placeholder.IsGranular;
                lastAddress = placeholder.EndAddress;
            }
        }
    }
}