[HLE] Resolve single-view backing reads and writes without the views lock

Every HLE access to aliased guest memory took one lock shared by all guest threads, and
Demon's Souls' job threads queued on it for 8-byte mutex reads. Readers now binary-search a
copy of the view list; mutations drop the copy under the lock and the next reader rebuilds
it. A reader that races a mutation uses the list from before it, as if it ran first.
This commit is contained in:
kuba 2026-09-30 10:06:24 +02:00
parent 3fe148a262
commit a01cffeb7b
1 changed files with 76 additions and 0 deletions

View File

@ -19,6 +19,12 @@ public sealed unsafe class SharedBackingViews : IDisposable
private readonly HostBackingObject? _backing;
private readonly object _lock = new();
private readonly SortedList<ulong, ViewRecord> _views = new();
// Readers search this copy of _views without the lock: every HLE call that reads or writes
// aliased guest memory comes through here, and one lock across all guest threads made them
// queue (Demon's Souls' job threads spent seconds contending on it for 8-byte mutex reads).
// Mutations drop it under the lock; the next reader rebuilds it. A reader racing a mutation
// uses the copy from before it, as if it ran first.
private ViewRecord[]? _snapshot;
private bool _disposed;
public SharedBackingViews(IHostViewMemory host, ulong size)
@ -49,6 +55,12 @@ public sealed unsafe class SharedBackingViews : IDisposable
public bool TryWriteBacking(ulong address, ReadOnlySpan<byte> data)
{
if (TryResolveSingleView(address, (ulong)data.Length, out var aliasAddress))
{
data.CopyTo(new Span<byte>((void*)aliasAddress, data.Length));
return true;
}
lock (_lock)
{
if (IsAvailable && TryFindRecord(address, (ulong)data.Length, out var record))
@ -79,6 +91,12 @@ public sealed unsafe class SharedBackingViews : IDisposable
public bool TryReadBacking(ulong address, Span<byte> data)
{
if (TryResolveSingleView(address, (ulong)data.Length, out var aliasAddress))
{
new ReadOnlySpan<byte>((void*)aliasAddress, data.Length).CopyTo(data);
return true;
}
lock (_lock)
{
// A read inside one mapping needs no temporary segment list.
@ -194,6 +212,7 @@ public sealed unsafe class SharedBackingViews : IDisposable
}
_views[address] = new ViewRecord(address, size, offset, protection) { WasRestored = wasRestored };
Volatile.Write(ref _snapshot, null);
}
failure = HostViewFailure.None;
@ -268,6 +287,7 @@ public sealed unsafe class SharedBackingViews : IDisposable
{
old = record;
_views.RemoveAt(index);
Volatile.Write(ref _snapshot, null);
}
}
}
@ -385,6 +405,7 @@ public sealed unsafe class SharedBackingViews : IDisposable
_disposed = true;
views = new List<ViewRecord>(_views.Values);
_views.Clear();
Volatile.Write(ref _snapshot, null);
}
foreach (var view in views)
@ -471,6 +492,60 @@ public sealed unsafe class SharedBackingViews : IDisposable
return false;
}
// The alias address of a range that lies inside one view, found without the lock.
private bool TryResolveSingleView(ulong address, ulong size, out ulong aliasAddress)
{
aliasAddress = 0;
if (!IsAvailable || size == 0 || ulong.MaxValue - address < size)
{
return false;
}
var views = Volatile.Read(ref _snapshot);
if (views is null)
{
lock (_lock)
{
views = _snapshot ??= [.. _views.Values];
}
}
int low = 0, high = views.Length - 1, found = -1;
while (low <= high)
{
var middle = (low + high) >>> 1;
if (views[middle].Address <= address)
{
found = middle;
low = middle + 1;
}
else
{
high = middle - 1;
}
}
if (found < 0)
{
return false;
}
var record = views[found];
if (address + size > record.Address + record.Size)
{
return false;
}
var offset = record.Offset + address - record.Address;
if (!IsWithinBacking(offset, size))
{
return false;
}
aliasAddress = AliasBase + offset;
return true;
}
private bool TryFindRecord(ulong address, ulong size, out ViewRecord record)
{
record = default;
@ -503,6 +578,7 @@ public sealed unsafe class SharedBackingViews : IDisposable
lock (_lock)
{
_views[record.Address] = record;
Volatile.Write(ref _snapshot, null);
}
}