Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 53 additions & 72 deletions src/Emulator/Main/Core/Machine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,9 @@ public Machine(bool createLocalTimeSource = false)
SetLocalName(SystemBus, SystemBusName);
gdbStubs = new Dictionary<int, GdbStub>();

invalidatedAddressesByCpu = new Dictionary<ICPU, List<long>>();
invalidatedAddressesByArchitecture = new Dictionary<string, List<long>>();
invalidatedAddressesLock = new object();
firstUnbroadcastedDirtyAddressIndex = new Dictionary<ICPU, int>();
dirtyPagesLock = new object();
dirtyPagesForConsumer = new Dictionary<ICPU, HashSet<long>>();
cpusNeedingFullFlush = new HashSet<ICPU>();

if(createLocalTimeSource)
{
Expand Down Expand Up @@ -276,36 +275,54 @@ public void HandleTimeDomainEvent<T>(Action<T> 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<long>();
}
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);
}
}

Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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))
Expand All @@ -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<long>();
}
}
}
if(ownLife != null)
Expand Down Expand Up @@ -1815,18 +1809,6 @@ private HashSet<IPeripheral> GetParents(IPeripheral child)
return parents;
}

private void InitializeInvalidatedAddressesList(ICPU cpu)
{
lock(invalidatedAddressesLock)
{
if(!invalidatedAddressesByArchitecture.TryGetValue(cpu.Architecture, out var invalidatedAddressesList))
{
invalidatedAddressesList = new List<long>() { Capacity = InitialDirtyListLength };
invalidatedAddressesByArchitecture.Add(cpu.Architecture, invalidatedAddressesList);
}
invalidatedAddressesByCpu[cpu] = invalidatedAddressesList;
}
}

private int currentStampLevel;
private bool alreadyDisposed;
Expand All @@ -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<string, List<long>> invalidatedAddressesByArchitecture;
private readonly Dictionary<ICPU, List<long>> invalidatedAddressesByCpu;

private readonly Dictionary<ICPU, int> firstUnbroadcastedDirtyAddressIndex;
private readonly object dirtyPagesLock;
// Key: consumer CPU, Value: set of dirty page addresses it hasn't seen yet
private readonly Dictionary<ICPU, HashSet<long>> dirtyPagesForConsumer;
// CPUs that were halted when dirty pages were appended — need full TLB flush on resume
private readonly HashSet<ICPU> cpusNeedingFullFlush;

/*
* Variables used for memory invalidation
Expand All @@ -1877,7 +1859,6 @@ private void InitializeInvalidatedAddressesList(ICPU cpu)
private readonly MultiTree<IPeripheral, IRegistrationPoint> registeredPeripherals;
private readonly object disposedSync;

private const int InitialDirtyListLength = 1 << 16;

private sealed class PausedState : IDisposable
{
Expand Down
10 changes: 10 additions & 0 deletions src/Emulator/Peripherals/Peripherals/CPU/TranslationCPU.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down