From a01cffeb7bf430dd795b32f6075b9cbf64743774 Mon Sep 17 00:00:00 2001 From: kuba Date: Wed, 30 Sep 2026 10:06:24 +0200 Subject: [PATCH] [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. --- .../GuestMemory/SharedBackingViews.cs | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/SharpEmu.HLE/GuestMemory/SharedBackingViews.cs b/src/SharpEmu.HLE/GuestMemory/SharedBackingViews.cs index dc964ae..a6bd5cc 100644 --- a/src/SharpEmu.HLE/GuestMemory/SharedBackingViews.cs +++ b/src/SharpEmu.HLE/GuestMemory/SharedBackingViews.cs @@ -19,6 +19,12 @@ public sealed unsafe class SharedBackingViews : IDisposable private readonly HostBackingObject? _backing; private readonly object _lock = new(); private readonly SortedList _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 data) { + if (TryResolveSingleView(address, (ulong)data.Length, out var aliasAddress)) + { + data.CopyTo(new Span((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 data) { + if (TryResolveSingleView(address, (ulong)data.Length, out var aliasAddress)) + { + new ReadOnlySpan((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(_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); } }