Ryujinx/ChocolArm64/Memory/AMemory.cs
gdkchan c393cdf8e3
More flexible memory manager (#307)
* Keep track mapped buffers with fixed offsets

* Started rewriting the memory manager

* Initial support for MapPhysicalMemory and UnmapPhysicalMemory, other tweaks

* MapPhysicalMemory/UnmapPhysicalMemory support, other tweaks

* Rebased

* Optimize the map/unmap physical memory svcs

* Integrate shared font support

* Fix address space reserve alignment

* Some fixes related to gpu memory mapping

* Some cleanup

* Only try uploading const buffers that are really used

* Check if memory region is contiguous

* Rebased

* Add missing count increment on IsRegionModified

* Check for reads/writes outside of the address space, optimize translation with a tail call
2018-08-15 15:59:51 -03:00

709 lines
19 KiB
C#

using ChocolArm64.Exceptions;
using ChocolArm64.State;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using System.Threading;
namespace ChocolArm64.Memory
{
public unsafe class AMemory : IAMemory, IDisposable
{
private const int PTLvl0Bits = 13;
private const int PTLvl1Bits = 14;
private const int PTPageBits = 12;
private const int PTLvl0Size = 1 << PTLvl0Bits;
private const int PTLvl1Size = 1 << PTLvl1Bits;
public const int PageSize = 1 << PTPageBits;
private const int PTLvl0Mask = PTLvl0Size - 1;
private const int PTLvl1Mask = PTLvl1Size - 1;
public const int PageMask = PageSize - 1;
private const int PTLvl0Bit = PTPageBits + PTLvl1Bits;
private const int PTLvl1Bit = PTPageBits;
private const long ErgMask = (4 << AThreadState.ErgSizeLog2) - 1;
private class ArmMonitor
{
public long Position;
public bool ExState;
public bool HasExclusiveAccess(long Position)
{
return this.Position == Position && ExState;
}
}
private Dictionary<int, ArmMonitor> Monitors;
private ConcurrentDictionary<long, IntPtr> ObservedPages;
public IntPtr Ram { get; private set; }
private byte* RamPtr;
private byte*** PageTable;
public AMemory(IntPtr Ram)
{
Monitors = new Dictionary<int, ArmMonitor>();
ObservedPages = new ConcurrentDictionary<long, IntPtr>();
this.Ram = Ram;
RamPtr = (byte*)Ram;
PageTable = (byte***)Marshal.AllocHGlobal(PTLvl0Size * IntPtr.Size);
for (int L0 = 0; L0 < PTLvl0Size; L0++)
{
PageTable[L0] = null;
}
}
public void RemoveMonitor(AThreadState State)
{
lock (Monitors)
{
ClearExclusive(State);
Monitors.Remove(State.ThreadId);
}
}
public void SetExclusive(AThreadState ThreadState, long Position)
{
Position &= ~ErgMask;
lock (Monitors)
{
foreach (ArmMonitor Mon in Monitors.Values)
{
if (Mon.Position == Position && Mon.ExState)
{
Mon.ExState = false;
}
}
if (!Monitors.TryGetValue(ThreadState.ThreadId, out ArmMonitor ThreadMon))
{
ThreadMon = new ArmMonitor();
Monitors.Add(ThreadState.ThreadId, ThreadMon);
}
ThreadMon.Position = Position;
ThreadMon.ExState = true;
}
}
public bool TestExclusive(AThreadState ThreadState, long Position)
{
//Note: Any call to this method also should be followed by a
//call to ClearExclusiveForStore if this method returns true.
Position &= ~ErgMask;
Monitor.Enter(Monitors);
if (!Monitors.TryGetValue(ThreadState.ThreadId, out ArmMonitor ThreadMon))
{
return false;
}
bool ExState = ThreadMon.HasExclusiveAccess(Position);
if (!ExState)
{
Monitor.Exit(Monitors);
}
return ExState;
}
public void ClearExclusiveForStore(AThreadState ThreadState)
{
if (Monitors.TryGetValue(ThreadState.ThreadId, out ArmMonitor ThreadMon))
{
ThreadMon.ExState = false;
}
Monitor.Exit(Monitors);
}
public void ClearExclusive(AThreadState ThreadState)
{
lock (Monitors)
{
if (Monitors.TryGetValue(ThreadState.ThreadId, out ArmMonitor ThreadMon))
{
ThreadMon.ExState = false;
}
}
}
public void WriteInt32ToSharedAddr(long Position, int Value)
{
long MaskedPosition = Position & ~ErgMask;
lock (Monitors)
{
foreach (ArmMonitor Mon in Monitors.Values)
{
if (Mon.Position == MaskedPosition && Mon.ExState)
{
Mon.ExState = false;
}
}
WriteInt32(Position, Value);
}
}
public sbyte ReadSByte(long Position)
{
return (sbyte)ReadByte(Position);
}
public short ReadInt16(long Position)
{
return (short)ReadUInt16(Position);
}
public int ReadInt32(long Position)
{
return (int)ReadUInt32(Position);
}
public long ReadInt64(long Position)
{
return (long)ReadUInt64(Position);
}
public byte ReadByte(long Position)
{
return *((byte*)Translate(Position));
}
public ushort ReadUInt16(long Position)
{
return *((ushort*)Translate(Position));
}
public uint ReadUInt32(long Position)
{
return *((uint*)Translate(Position));
}
public ulong ReadUInt64(long Position)
{
return *((ulong*)Translate(Position));
}
public Vector128<float> ReadVector8(long Position)
{
if (Sse2.IsSupported)
{
return Sse.StaticCast<byte, float>(Sse2.SetVector128(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ReadByte(Position)));
}
else
{
throw new PlatformNotSupportedException();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector128<float> ReadVector16(long Position)
{
if (Sse2.IsSupported)
{
return Sse.StaticCast<ushort, float>(Sse2.Insert(Sse2.SetZeroVector128<ushort>(), ReadUInt16(Position), 0));
}
else
{
throw new PlatformNotSupportedException();
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public Vector128<float> ReadVector32(long Position)
{
if (Sse.IsSupported)
{
return Sse.LoadScalarVector128((float*)Translate(Position));
}
else
{
throw new PlatformNotSupportedException();
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public Vector128<float> ReadVector64(long Position)
{
if (Sse2.IsSupported)
{
return Sse.StaticCast<double, float>(Sse2.LoadScalarVector128((double*)Translate(Position)));
}
else
{
throw new PlatformNotSupportedException();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector128<float> ReadVector128(long Position)
{
if (Sse.IsSupported)
{
return Sse.LoadVector128((float*)Translate(Position));
}
else
{
throw new PlatformNotSupportedException();
}
}
public byte[] ReadBytes(long Position, long Size)
{
if ((uint)Size > int.MaxValue)
{
throw new ArgumentOutOfRangeException(nameof(Size));
}
EnsureRangeIsValid(Position, Size);
byte[] Data = new byte[Size];
Marshal.Copy((IntPtr)Translate(Position), Data, 0, (int)Size);
return Data;
}
public void WriteSByte(long Position, sbyte Value)
{
WriteByte(Position, (byte)Value);
}
public void WriteInt16(long Position, short Value)
{
WriteUInt16(Position, (ushort)Value);
}
public void WriteInt32(long Position, int Value)
{
WriteUInt32(Position, (uint)Value);
}
public void WriteInt64(long Position, long Value)
{
WriteUInt64(Position, (ulong)Value);
}
public void WriteByte(long Position, byte Value)
{
*((byte*)TranslateWrite(Position)) = Value;
}
public void WriteUInt16(long Position, ushort Value)
{
*((ushort*)TranslateWrite(Position)) = Value;
}
public void WriteUInt32(long Position, uint Value)
{
*((uint*)TranslateWrite(Position)) = Value;
}
public void WriteUInt64(long Position, ulong Value)
{
*((ulong*)TranslateWrite(Position)) = Value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteVector8(long Position, Vector128<float> Value)
{
if (Sse41.IsSupported)
{
WriteByte(Position, Sse41.Extract(Sse.StaticCast<float, byte>(Value), 0));
}
else if (Sse2.IsSupported)
{
WriteByte(Position, (byte)Sse2.Extract(Sse.StaticCast<float, ushort>(Value), 0));
}
else
{
throw new PlatformNotSupportedException();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteVector16(long Position, Vector128<float> Value)
{
if (Sse2.IsSupported)
{
WriteUInt16(Position, Sse2.Extract(Sse.StaticCast<float, ushort>(Value), 0));
}
else
{
throw new PlatformNotSupportedException();
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public void WriteVector32(long Position, Vector128<float> Value)
{
if (Sse.IsSupported)
{
Sse.StoreScalar((float*)TranslateWrite(Position), Value);
}
else
{
throw new PlatformNotSupportedException();
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public void WriteVector64(long Position, Vector128<float> Value)
{
if (Sse2.IsSupported)
{
Sse2.StoreScalar((double*)TranslateWrite(Position), Sse.StaticCast<float, double>(Value));
}
else
{
throw new PlatformNotSupportedException();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteVector128(long Position, Vector128<float> Value)
{
if (Sse.IsSupported)
{
Sse.Store((float*)TranslateWrite(Position), Value);
}
else
{
throw new PlatformNotSupportedException();
}
}
public void WriteBytes(long Position, byte[] Data)
{
EnsureRangeIsValid(Position, (uint)Data.Length);
Marshal.Copy(Data, 0, (IntPtr)TranslateWrite(Position), Data.Length);
}
public void Map(long VA, long PA, long Size)
{
SetPTEntries(VA, RamPtr + PA, Size);
}
public void Unmap(long Position, long Size)
{
SetPTEntries(Position, null, Size);
StopObservingRegion(Position, Size);
}
public bool IsMapped(long Position)
{
if (!(IsValidPosition(Position)))
{
return false;
}
long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask;
long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask;
if (PageTable[L0] == null)
{
return false;
}
return PageTable[L0][L1] != null || ObservedPages.ContainsKey(Position >> PTPageBits);
}
public long GetPhysicalAddress(long VirtualAddress)
{
byte* Ptr = Translate(VirtualAddress);
return (long)(Ptr - RamPtr);
}
internal byte* Translate(long Position)
{
long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask;
long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask;
long Old = Position;
byte** Lvl1 = PageTable[L0];
if ((Position >> (PTLvl0Bit + PTLvl0Bits)) != 0)
{
goto Unmapped;
}
if (Lvl1 == null)
{
goto Unmapped;
}
Position &= PageMask;
byte* Ptr = Lvl1[L1];
if (Ptr == null)
{
goto Unmapped;
}
return Ptr + Position;
Unmapped:
return HandleNullPte(Old);
}
private byte* HandleNullPte(long Position)
{
long Key = Position >> PTPageBits;
if (ObservedPages.TryGetValue(Key, out IntPtr Ptr))
{
return (byte*)Ptr + (Position & PageMask);
}
throw new VmmPageFaultException(Position);
}
internal byte* TranslateWrite(long Position)
{
long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask;
long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask;
long Old = Position;
byte** Lvl1 = PageTable[L0];
if ((Position >> (PTLvl0Bit + PTLvl0Bits)) != 0)
{
goto Unmapped;
}
if (Lvl1 == null)
{
goto Unmapped;
}
Position &= PageMask;
byte* Ptr = Lvl1[L1];
if (Ptr == null)
{
goto Unmapped;
}
return Ptr + Position;
Unmapped:
return HandleNullPteWrite(Old);
}
private byte* HandleNullPteWrite(long Position)
{
long Key = Position >> PTPageBits;
if (ObservedPages.TryGetValue(Key, out IntPtr Ptr))
{
SetPTEntry(Position, (byte*)Ptr);
return (byte*)Ptr + (Position & PageMask);
}
throw new VmmPageFaultException(Position);
}
private void SetPTEntries(long VA, byte* Ptr, long Size)
{
long EndPosition = (VA + Size + PageMask) & ~PageMask;
while ((ulong)VA < (ulong)EndPosition)
{
SetPTEntry(VA, Ptr);
VA += PageSize;
if (Ptr != null)
{
Ptr += PageSize;
}
}
}
private void SetPTEntry(long Position, byte* Ptr)
{
if (!IsValidPosition(Position))
{
throw new ArgumentOutOfRangeException(nameof(Position));
}
long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask;
long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask;
if (PageTable[L0] == null)
{
byte** Lvl1 = (byte**)Marshal.AllocHGlobal(PTLvl1Size * IntPtr.Size);
for (int ZL1 = 0; ZL1 < PTLvl1Size; ZL1++)
{
Lvl1[ZL1] = null;
}
Thread.MemoryBarrier();
PageTable[L0] = Lvl1;
}
PageTable[L0][L1] = Ptr;
}
public (bool[], int) IsRegionModified(long Position, long Size)
{
long EndPosition = (Position + Size + PageMask) & ~PageMask;
Position &= ~PageMask;
Size = EndPosition - Position;
bool[] Modified = new bool[Size >> PTPageBits];
int Count = 0;
lock (ObservedPages)
{
for (int Page = 0; Page < Modified.Length; Page++)
{
byte* Ptr = Translate(Position);
if (ObservedPages.TryAdd(Position >> PTPageBits, (IntPtr)Ptr))
{
Modified[Page] = true;
Count++;
}
else
{
long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask;
long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask;
byte** Lvl1 = PageTable[L0];
if (Lvl1 != null)
{
if (Modified[Page] = Lvl1[L1] != null)
{
Count++;
}
}
}
SetPTEntry(Position, null);
Position += PageSize;
}
}
return (Modified, Count);
}
public void StopObservingRegion(long Position, long Size)
{
long EndPosition = (Position + Size + PageMask) & ~PageMask;
while (Position < EndPosition)
{
lock (ObservedPages)
{
if (ObservedPages.TryRemove(Position >> PTPageBits, out IntPtr Ptr))
{
SetPTEntry(Position, (byte*)Ptr);
}
}
Position += PageSize;
}
}
public IntPtr GetHostAddress(long Position, long Size)
{
EnsureRangeIsValid(Position, Size);
return (IntPtr)Translate(Position);
}
internal void EnsureRangeIsValid(long Position, long Size)
{
long EndPos = Position + Size;
Position &= ~PageMask;
long ExpectedPA = GetPhysicalAddress(Position);
while ((ulong)Position < (ulong)EndPos)
{
long PA = GetPhysicalAddress(Position);
if (PA != ExpectedPA)
{
throw new VmmAccessException(Position, Size);
}
Position += PageSize;
ExpectedPA += PageSize;
}
}
public bool IsValidPosition(long Position)
{
return Position >> (PTLvl0Bits + PTLvl1Bits + PTPageBits) == 0;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (PageTable == null)
{
return;
}
for (int L0 = 0; L0 < PTLvl0Size; L0++)
{
if (PageTable[L0] != null)
{
Marshal.FreeHGlobal((IntPtr)PageTable[L0]);
}
PageTable[L0] = null;
}
Marshal.FreeHGlobal((IntPtr)PageTable);
PageTable = null;
}
}
}