diff --git a/src/Emulator/Main/Core/Machine.cs b/src/Emulator/Main/Core/Machine.cs index 0978f68e4..a504e4b73 100644 --- a/src/Emulator/Main/Core/Machine.cs +++ b/src/Emulator/Main/Core/Machine.cs @@ -60,10 +60,9 @@ public Machine(bool createLocalTimeSource = false) SetLocalName(SystemBus, SystemBusName); gdbStubs = new Dictionary(); - invalidatedAddressesByCpu = new Dictionary>(); - invalidatedAddressesByArchitecture = new Dictionary>(); - invalidatedAddressesLock = new object(); - firstUnbroadcastedDirtyAddressIndex = new Dictionary(); + dirtyPagesLock = new object(); + dirtyPagesForConsumer = new Dictionary>(); + cpusNeedingFullFlush = new HashSet(); if(createLocalTimeSource) { @@ -276,36 +275,54 @@ public void HandleTimeDomainEvent(Action handler, T handlerArgument, bool public long[] GetNewDirtyAddressesForCore(ICPU cpu) { - if(!firstUnbroadcastedDirtyAddressIndex.ContainsKey(cpu)) + lock(dirtyPagesLock) { - throw new RecoverableException($"No entries for a cpu: {cpu.GetName()}. Was the cpu registered properly?"); - } + if(!dirtyPagesForConsumer.ContainsKey(cpu)) + { + throw new RecoverableException($"No entries for a cpu: {cpu.GetName()}. Was the cpu registered properly?"); + } - long[] newAddresses; - lock(invalidatedAddressesLock) - { - var firstUnsentIndex = firstUnbroadcastedDirtyAddressIndex[cpu]; - var addressesCount = invalidatedAddressesByCpu[cpu].Count - firstUnsentIndex; - newAddresses = invalidatedAddressesByCpu[cpu].GetRange(firstUnsentIndex, addressesCount).ToArray(); - firstUnbroadcastedDirtyAddressIndex[cpu] += addressesCount; + // If this CPU was halted while others wrote code pages, + // signal the caller to do a full TLB flush instead. + if(cpusNeedingFullFlush.Remove(cpu)) + { + dirtyPagesForConsumer[cpu].Clear(); + return null; + } + + var pageSet = dirtyPagesForConsumer[cpu]; + if(pageSet.Count == 0) + { + return Array.Empty(); + } + var result = pageSet.ToArray(); + pageSet.Clear(); + return result; } - return newAddresses; } public void AppendDirtyAddresses(ICPU cpu, long[] addresses) { - if(!invalidatedAddressesByCpu.ContainsKey(cpu)) + lock(dirtyPagesLock) { - throw new RecoverableException($"Invalid cpu: {cpu.GetName()}"); - } - - lock(invalidatedAddressesLock) - { - if(invalidatedAddressesByCpu[cpu].Count + addresses.Length > invalidatedAddressesByCpu[cpu].Capacity) + foreach(var entry in dirtyPagesForConsumer) { - TryReduceBroadcastedDirtyAddresses(cpu); + var consumer = entry.Key; + if(consumer == cpu || consumer.Architecture != cpu.Architecture) + { + continue; + } + if(consumer.IsHalted) + { + cpusNeedingFullFlush.Add(consumer); + continue; + } + var pageSet = entry.Value; + foreach(var addr in addresses) + { + pageSet.Add(addr); + } } - invalidatedAddressesByCpu[cpu].AddRange(addresses); } } @@ -1653,12 +1670,10 @@ private void InnerUnregisterFromParent(IPeripheral peripheral) { throw new RecoverableException($"{cpu.Model ?? "Unknown model"}: CPU architecture not provided"); } - invalidatedAddressesByCpu.Remove(cpu); - firstUnbroadcastedDirtyAddressIndex.Remove(cpu); - - if(!invalidatedAddressesByCpu.Any(pair => pair.Key.Architecture == cpu.Architecture)) + lock(dirtyPagesLock) { - invalidatedAddressesByArchitecture.Remove(cpu.Architecture); + dirtyPagesForConsumer.Remove(cpu); + cpusNeedingFullFlush.Remove(cpu); } } @@ -1686,29 +1701,6 @@ private void InnerUnregisterFromParent(IPeripheral peripheral) } } - private void TryReduceBroadcastedDirtyAddresses(ICPU cpu) - { - var sameArchitectureCPUs = firstUnbroadcastedDirtyAddressIndex - .Where(pair => pair.Key.Architecture == cpu.Architecture) - .ToArray(); - - var firstUnread = sameArchitectureCPUs.Select(pair => pair.Value).Min(); - if(firstUnread == 0) - { - var laggingCPUNames = sameArchitectureCPUs.Where(pair => pair.Value == 0).Select(pair => pair.Key.GetName()); - cpu.DebugLog( - "Attempted reduction of {0} dirty addresses list failed, current count: {1}, CPUs that didn't fetch any: {2}", - cpu.Architecture, invalidatedAddressesByCpu[cpu].Count, string.Join(", ", laggingCPUNames)); - return; - } - - invalidatedAddressesByCpu[cpu].RemoveRange(0, (int)firstUnread); - foreach(var key in sameArchitectureCPUs.Select(pair => pair.Key)) - { - firstUnbroadcastedDirtyAddressIndex[key] -= firstUnread; - } - } - private void Register(IPeripheral peripheral, IRegistrationPoint registrationPoint, IPeripheral parent) { using(ObtainPausedState(true)) @@ -1728,8 +1720,10 @@ private void Register(IPeripheral peripheral, IRegistrationPoint registrationPoi if(architecturesWithBroadcastSupport.Contains(cpu.Architecture)) { - InitializeInvalidatedAddressesList(cpu); - firstUnbroadcastedDirtyAddressIndex[cpu] = 0; + lock(dirtyPagesLock) + { + dirtyPagesForConsumer[cpu] = new HashSet(); + } } } if(ownLife != null) @@ -1815,18 +1809,6 @@ private HashSet GetParents(IPeripheral child) return parents; } - private void InitializeInvalidatedAddressesList(ICPU cpu) - { - lock(invalidatedAddressesLock) - { - if(!invalidatedAddressesByArchitecture.TryGetValue(cpu.Architecture, out var invalidatedAddressesList)) - { - invalidatedAddressesList = new List() { Capacity = InitialDirtyListLength }; - invalidatedAddressesByArchitecture.Add(cpu.Architecture, invalidatedAddressesList); - } - invalidatedAddressesByCpu[cpu] = invalidatedAddressesList; - } - } private int currentStampLevel; private bool alreadyDisposed; @@ -1849,11 +1831,11 @@ private void InitializeInvalidatedAddressesList(ICPU cpu) private readonly object recorderPlayerLock = new object(); private readonly BaseClockSource clockSource; - private readonly object invalidatedAddressesLock; - private readonly Dictionary> invalidatedAddressesByArchitecture; - private readonly Dictionary> invalidatedAddressesByCpu; - - private readonly Dictionary firstUnbroadcastedDirtyAddressIndex; + private readonly object dirtyPagesLock; + // Key: consumer CPU, Value: set of dirty page addresses it hasn't seen yet + private readonly Dictionary> dirtyPagesForConsumer; + // CPUs that were halted when dirty pages were appended — need full TLB flush on resume + private readonly HashSet cpusNeedingFullFlush; /* * Variables used for memory invalidation @@ -1877,7 +1859,6 @@ private void InitializeInvalidatedAddressesList(ICPU cpu) private readonly MultiTree registeredPeripherals; private readonly object disposedSync; - private const int InitialDirtyListLength = 1 << 16; private sealed class PausedState : IDisposable { diff --git a/src/Emulator/Peripherals/Peripherals/CPU/TranslationCPU.cs b/src/Emulator/Peripherals/Peripherals/CPU/TranslationCPU.cs index 3411f5f0d..4335d180d 100644 --- a/src/Emulator/Peripherals/Peripherals/CPU/TranslationCPU.cs +++ b/src/Emulator/Peripherals/Peripherals/CPU/TranslationCPU.cs @@ -1704,6 +1704,16 @@ private void OnTranslationBlockFetch(ulong offset) private IntPtr GetDirty(IntPtr size) { var dirtyAddressesList = machine.GetNewDirtyAddressesForCore(this); + + // null means this CPU was halted while others wrote code pages — + // do a full translation cache flush to ensure no stale translations. + if(dirtyAddressesList == null) + { + TlibInvalidateTranslationCache(); + Marshal.WriteInt64(size, 0); + return dirtyAddressesPtr; + } + var newAddressesCount = dirtyAddressesList.Length; if(newAddressesCount > 0)