Implement a new physical memory manager and replace DeviceMemory

This commit is contained in:
gdkchan 2020-01-02 00:23:47 -03:00
parent 87bfe681ef
commit 4f5b582df5
19 changed files with 887 additions and 71 deletions

View file

@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Font
{
private Switch _device;
private long _physicalAddress;
private ulong _physicalAddress;
private string _fontsPath;
@ -35,7 +35,7 @@ namespace Ryujinx.HLE.HOS.Font
private Dictionary<SharedFontType, FontInfo> _fontData;
public SharedFontManager(Switch device, long physicalAddress)
public SharedFontManager(Switch device, ulong physicalAddress)
{
_physicalAddress = physicalAddress;
@ -48,7 +48,7 @@ namespace Ryujinx.HLE.HOS.Font
{
if (_fontData == null)
{
_device.Memory.FillWithZeros(_physicalAddress, Horizon.FontSize);
_device.Memory.ZeroFill(_physicalAddress, Horizon.FontSize);
uint fontOffset = 0;
@ -84,7 +84,7 @@ namespace Ryujinx.HLE.HOS.Font
for (; fontOffset - start < data.Length; fontOffset++)
{
_device.Memory.WriteByte(_physicalAddress + fontOffset, data[fontOffset - start]);
_device.Memory.Write(_physicalAddress + fontOffset, data[fontOffset - start]);
}
return info;
@ -107,7 +107,7 @@ namespace Ryujinx.HLE.HOS.Font
for (; fontOffset - start < data.Length; fontOffset++)
{
_device.Memory.WriteByte(_physicalAddress + fontOffset, data[fontOffset - start]);
_device.Memory.Write(_physicalAddress + fontOffset, data[fontOffset - start]);
}
return info;
@ -138,15 +138,15 @@ namespace Ryujinx.HLE.HOS.Font
}
}
private void WriteMagicAndSize(long position, int size)
private void WriteMagicAndSize(ulong address, int size)
{
const int decMagic = 0x18029a7f;
const int key = 0x49621806;
int encryptedSize = BinaryPrimitives.ReverseEndianness(size ^ key);
_device.Memory.WriteInt32(position + 0, decMagic);
_device.Memory.WriteInt32(position + 4, encryptedSize);
_device.Memory.Write(address + 0, decMagic);
_device.Memory.Write(address + 4, encryptedSize);
}
public int GetFontSize(SharedFontType fontType)

View file

@ -190,13 +190,13 @@ namespace Ryujinx.HLE.HOS
KSharedMemory timeSharedMemory = new KSharedMemory(this, timePageList, 0, 0, MemoryPermission.Read);
TimeServiceManager.Instance.Initialize(device, this, timeSharedMemory, (long)(timePa - DramMemoryMap.DramBase), TimeSize);
TimeServiceManager.Instance.Initialize(device, this, timeSharedMemory, timePa - DramMemoryMap.DramBase, TimeSize);
AppletState = new AppletStateMgr(this);
AppletState.SetFocus(true);
Font = new SharedFontManager(device, (long)(fontPa - DramMemoryMap.DramBase));
Font = new SharedFontManager(device, fontPa - DramMemoryMap.DramBase);
IUserInterface.InitializePort(this);

View file

@ -352,7 +352,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
int newHandle = 0;
int handle = System.Device.Memory.ReadInt32((long)clientMsg.DramAddress + offset * 4);
int handle = System.Device.Memory.Read<int>(clientMsg.DramAddress + offset * 4);
if (clientResult == KernelResult.Success && handle != 0)
{
@ -368,7 +368,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
int newHandle = 0;
int handle = System.Device.Memory.ReadInt32((long)clientMsg.DramAddress + offset * 4);
int handle = System.Device.Memory.Read<int>(clientMsg.DramAddress + offset * 4);
if (handle != 0)
{
@ -404,7 +404,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
for (int index = 0; index < clientHeader.PointerBuffersCount; index++)
{
ulong pointerDesc = System.Device.Memory.ReadUInt64((long)clientMsg.DramAddress + offset * 4);
ulong pointerDesc = System.Device.Memory.Read<ulong>(clientMsg.DramAddress + offset * 4);
PointerBufferDesc descriptor = new PointerBufferDesc(pointerDesc);
@ -463,11 +463,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
for (int index = 0; index < totalBuffersCount; index++)
{
long clientDescAddress = (long)clientMsg.DramAddress + offset * 4;
ulong clientDescAddress = clientMsg.DramAddress + offset * 4;
uint descWord0 = System.Device.Memory.ReadUInt32(clientDescAddress + 0);
uint descWord1 = System.Device.Memory.ReadUInt32(clientDescAddress + 4);
uint descWord2 = System.Device.Memory.ReadUInt32(clientDescAddress + 8);
uint descWord0 = System.Device.Memory.Read<uint>(clientDescAddress + 0);
uint descWord1 = System.Device.Memory.Read<uint>(clientDescAddress + 4);
uint descWord2 = System.Device.Memory.Read<uint>(clientDescAddress + 8);
bool isSendDesc = index < clientHeader.SendBuffersCount;
bool isExchangeDesc = index >= clientHeader.SendBuffersCount + clientHeader.ReceiveBuffersCount;
@ -700,8 +700,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
}
// Copy header.
System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 0, serverHeader.Word0);
System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 4, serverHeader.Word1);
System.Device.Memory.Write(clientMsg.DramAddress + 0, serverHeader.Word0);
System.Device.Memory.Write(clientMsg.DramAddress + 4, serverHeader.Word1);
// Copy handles.
uint offset;
@ -710,11 +710,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
offset = 3;
System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 8, serverHeader.Word2);
System.Device.Memory.Write(clientMsg.DramAddress + 8, serverHeader.Word2);
if (serverHeader.HasPid)
{
System.Device.Memory.WriteInt64((long)clientMsg.DramAddress + offset * 4, serverProcess.Pid);
System.Device.Memory.Write(clientMsg.DramAddress + offset * 4, serverProcess.Pid);
offset += 2;
}
@ -730,7 +730,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
GetCopyObjectHandle(serverThread, clientProcess, handle, out newHandle);
}
System.Device.Memory.WriteInt32((long)clientMsg.DramAddress + offset * 4, newHandle);
System.Device.Memory.Write(clientMsg.DramAddress + offset * 4, newHandle);
offset++;
}
@ -753,7 +753,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
}
}
System.Device.Memory.WriteInt32((long)clientMsg.DramAddress + offset * 4, newHandle);
System.Device.Memory.Write(clientMsg.DramAddress + offset * 4, newHandle);
offset++;
}
@ -819,11 +819,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
for (int index = 0; index < totalBuffersCount; index++)
{
long dstDescAddress = (long)clientMsg.DramAddress + offset * 4;
ulong dstDescAddress = clientMsg.DramAddress + offset * 4;
System.Device.Memory.WriteUInt32(dstDescAddress + 0, 0);
System.Device.Memory.WriteUInt32(dstDescAddress + 4, 0);
System.Device.Memory.WriteUInt32(dstDescAddress + 8, 0);
System.Device.Memory.Write(dstDescAddress + 0, 0);
System.Device.Memory.Write(dstDescAddress + 4, 0);
System.Device.Memory.Write(dstDescAddress + 8, 0);
offset += 3;
}
@ -878,9 +878,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
private MessageHeader GetClientMessageHeader(Message clientMsg)
{
uint word0 = System.Device.Memory.ReadUInt32((long)clientMsg.DramAddress + 0);
uint word1 = System.Device.Memory.ReadUInt32((long)clientMsg.DramAddress + 4);
uint word2 = System.Device.Memory.ReadUInt32((long)clientMsg.DramAddress + 8);
uint word0 = System.Device.Memory.Read<uint>(clientMsg.DramAddress + 0);
uint word1 = System.Device.Memory.Read<uint>(clientMsg.DramAddress + 4);
uint word2 = System.Device.Memory.Read<uint>(clientMsg.DramAddress + 8);
return new MessageHeader(word0, word1, word2);
}
@ -970,11 +970,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
ulong[] receiveList = new ulong[recvListSize];
long recvListAddress = (long)message.DramAddress + recvListOffset;
ulong recvListAddress = message.DramAddress + recvListOffset;
for (int index = 0; index < recvListSize; index++)
{
receiveList[index] = System.Device.Memory.ReadUInt64(recvListAddress + index * 8);
receiveList[index] = System.Device.Memory.Read<ulong>(recvListAddress + (ulong)index * 8);
}
return receiveList;
@ -1225,8 +1225,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
ulong address = clientProcess.MemoryManager.GetDramAddressFromVa(request.CustomCmdBuffAddr);
System.Device.Memory.WriteInt64((long)address + 0, 0);
System.Device.Memory.WriteInt32((long)address + 8, (int)result);
System.Device.Memory.Write<ulong>(address, 0);
System.Device.Memory.Write(address + 8, (int)result);
clientProcess.MemoryManager.UnborrowIpcBuffer(
request.CustomCmdBuffAddr,

View file

@ -1840,7 +1840,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{
ulong unusedSizeBefore = address - addressTruncated;
_system.Device.Memory.Set(dstFirstPagePa, 0, unusedSizeBefore);
_system.Device.Memory.ZeroFill(dstFirstPagePa, unusedSizeBefore);
ulong copySize = addressRounded <= endAddr ? addressRounded - address : size;
@ -1859,7 +1859,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
if (unusedSizeAfter != 0)
{
_system.Device.Memory.Set(firstPageFillAddress, 0, unusedSizeAfter);
_system.Device.Memory.ZeroFill(firstPageFillAddress, unusedSizeAfter);
}
KPageList pages = new KPageList();
@ -1919,7 +1919,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
unusedSizeAfter = PageSize;
}
_system.Device.Memory.Set(lastPageFillAddr, 0, unusedSizeAfter);
_system.Device.Memory.ZeroFill(lastPageFillAddr, unusedSizeAfter);
if (pages.AddRange(dstFirstPagePa, 1) != KernelResult.Success)
{

View file

@ -1022,7 +1022,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
bool useFlatPageTable = memRegion == MemoryRegion.Application;
CpuMemory = new MemoryManager(_system.Device.Memory.RamPointer, addrSpaceBits, useFlatPageTable);
CpuMemory = new MemoryManager(_system.Device.Memory.Pointer, addrSpaceBits, useFlatPageTable);
Translator = new Translator(CpuMemory);

View file

@ -55,7 +55,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
EphemeralClockContextWriter = new EphemeralNetworkSystemClockContextWriter();
}
public void Initialize(Switch device, Horizon system, KSharedMemory sharedMemory, long timeSharedMemoryAddress, int timeSharedMemorySize)
public void Initialize(Switch device, Horizon system, KSharedMemory sharedMemory, ulong timeSharedMemoryAddress, int timeSharedMemorySize)
{
SharedMemory.Initialize(device, sharedMemory, timeSharedMemoryAddress, timeSharedMemorySize);

View file

@ -1,4 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using Ryujinx.HLE.HOS.Kernel.Memory;
@ -13,7 +14,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
{
private Switch _device;
private KSharedMemory _sharedMemory;
private long _timeSharedMemoryAddress;
private ulong _timeSharedMemoryAddress;
private int _timeSharedMemorySize;
private const uint SteadyClockContextOffset = 0x00;
@ -21,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
private const uint NetworkSystemClockContextOffset = 0x80;
private const uint AutomaticCorrectionEnabledOffset = 0xC8;
public void Initialize(Switch device, KSharedMemory sharedMemory, long timeSharedMemoryAddress, int timeSharedMemorySize)
public void Initialize(Switch device, KSharedMemory sharedMemory, ulong timeSharedMemoryAddress, int timeSharedMemorySize)
{
_device = device;
_sharedMemory = sharedMemory;
@ -29,7 +30,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
_timeSharedMemorySize = timeSharedMemorySize;
// Clean the shared memory
_device.Memory.FillWithZeros(_timeSharedMemoryAddress, _timeSharedMemorySize);
_device.Memory.ZeroFill(_timeSharedMemoryAddress, (ulong)_timeSharedMemorySize);
}
public KSharedMemory GetSharedMemory()
@ -86,9 +87,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
WriteObjectToSharedMemory(NetworkSystemClockContextOffset, 4, context);
}
private T ReadObjectFromSharedMemory<T>(long offset, long padding)
private T ReadObjectFromSharedMemory<T>(ulong offset, ulong padding)
{
long indexOffset = _timeSharedMemoryAddress + offset;
ulong indexOffset = _timeSharedMemoryAddress + offset;
T result;
uint index;
@ -96,31 +97,31 @@ namespace Ryujinx.HLE.HOS.Services.Time
do
{
index = _device.Memory.ReadUInt32(indexOffset);
index = _device.Memory.Read<uint>(indexOffset);
long objectOffset = indexOffset + 4 + padding + (index & 1) * Marshal.SizeOf<T>();
ulong objectOffset = indexOffset + 4 + padding + (ulong)((index & 1) * Unsafe.SizeOf<T>());
result = _device.Memory.ReadStruct<T>(objectOffset);
result = _device.Memory.Read<T>(objectOffset);
Thread.MemoryBarrier();
possiblyNewIndex = _device.Memory.ReadUInt32(indexOffset);
possiblyNewIndex = _device.Memory.Read<uint>(indexOffset);
} while (index != possiblyNewIndex);
return result;
}
private void WriteObjectToSharedMemory<T>(long offset, long padding, T value)
private void WriteObjectToSharedMemory<T>(ulong offset, ulong padding, T value)
{
long indexOffset = _timeSharedMemoryAddress + offset;
uint newIndex = _device.Memory.ReadUInt32(indexOffset) + 1;
long objectOffset = indexOffset + 4 + padding + (newIndex & 1) * Marshal.SizeOf<T>();
ulong indexOffset = _timeSharedMemoryAddress + offset;
uint newIndex = _device.Memory.Read<uint>(indexOffset) + 1;
ulong objectOffset = indexOffset + 4 + padding + (ulong)((newIndex & 1) * Unsafe.SizeOf<T>());
_device.Memory.WriteStruct(objectOffset, value);
_device.Memory.Write(objectOffset, value);
Thread.MemoryBarrier();
_device.Memory.WriteUInt32(indexOffset, newIndex);
_device.Memory.Write(indexOffset, newIndex);
}
}
}

View file

@ -88,9 +88,9 @@ namespace Ryujinx.HLE.Input
_mainLayoutOffset = Offset + HidControllerHeaderSize
+ ((int)ControllerLayouts.Main * HidControllerLayoutsSize);
Device.Memory.FillWithZeros(Offset, 0x5000);
Device.Memory.WriteStruct(Offset, Header);
Device.Memory.WriteStruct(DeviceStateOffset, DeviceState);
Device.Memory.ZeroFill((ulong)Offset, 0x5000);
Device.Memory.Write((ulong)Offset, Header);
Device.Memory.Write((ulong)DeviceStateOffset, DeviceState);
Connected = true;
}
@ -126,14 +126,14 @@ namespace Ryujinx.HLE.Input
Timestamp = GetTimestamp(),
};
Device.Memory.WriteStruct(_currentLayoutOffset, newInputStateHeader);
Device.Memory.WriteStruct(_mainLayoutOffset, newInputStateHeader);
Device.Memory.Write((ulong)_currentLayoutOffset, newInputStateHeader);
Device.Memory.Write((ulong)_mainLayoutOffset, newInputStateHeader);
long currentInputStateOffset = HidControllersLayoutHeaderSize
+ newInputStateHeader.CurrentEntryIndex * HidControllersInputEntrySize;
Device.Memory.WriteStruct(_currentLayoutOffset + currentInputStateOffset, currentInput);
Device.Memory.WriteStruct(_mainLayoutOffset + currentInputStateOffset, currentInput);
Device.Memory.Write((ulong)(_currentLayoutOffset + currentInputStateOffset), currentInput);
Device.Memory.Write((ulong)(_mainLayoutOffset + currentInputStateOffset), currentInput);
LastInputState = currentInput;
CurrentStateHeader = newInputStateHeader;

View file

@ -26,7 +26,7 @@ namespace Ryujinx.HLE.Input
_device = device;
HidPosition = hidPosition;
device.Memory.FillWithZeros(hidPosition, Horizon.HidSize);
device.Memory.ZeroFill((ulong)hidPosition, Horizon.HidSize);
_currentTouchHeader = new TouchHeader()
{
@ -152,7 +152,7 @@ namespace Ryujinx.HLE.Input
TouchCount = points.Length
};
_device.Memory.WriteStruct(currentTouchEntryOffset, touchEntry);
_device.Memory.Write((ulong)currentTouchEntryOffset, touchEntry);
currentTouchEntryOffset += HidTouchEntryHeaderSize;
@ -169,12 +169,12 @@ namespace Ryujinx.HLE.Input
Y = points[i].Y
};
_device.Memory.WriteStruct(currentTouchEntryOffset, touch);
_device.Memory.Write((ulong)currentTouchEntryOffset, touch);
currentTouchEntryOffset += HidTouchEntryTouchSize;
}
_device.Memory.WriteStruct(_touchScreenOffset, newTouchHeader);
_device.Memory.Write((ulong)_touchScreenOffset, newTouchHeader);
_currentTouchHeader = newTouchHeader;
}
@ -191,7 +191,7 @@ namespace Ryujinx.HLE.Input
Timestamp = timestamp,
};
_device.Memory.WriteStruct(_keyboardOffset, newKeyboardHeader);
_device.Memory.Write((ulong)_keyboardOffset, newKeyboardHeader);
long keyboardEntryOffset = _keyboardOffset + HidKeyboardHeaderSize;
keyboardEntryOffset += newKeyboardHeader.CurrentEntryIndex * HidKeyboardEntrySize;
@ -204,7 +204,7 @@ namespace Ryujinx.HLE.Input
Modifier = keyboard.Modifier,
};
_device.Memory.WriteStruct(keyboardEntryOffset, newkeyboardEntry);
_device.Memory.Write((ulong)keyboardEntryOffset, newkeyboardEntry);
_currentKeyboardEntry = newkeyboardEntry;
_currentKeyboardHeader = newKeyboardHeader;

View file

@ -45,6 +45,7 @@
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics\Ryujinx.Graphics.csproj" />
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
<ProjectReference Include="..\Ryujinx.Profiler\Ryujinx.Profiler.csproj" />
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
</ItemGroup>

View file

@ -8,6 +8,7 @@ using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Input;
using Ryujinx.Memory;
using System;
using System.Threading;
@ -17,7 +18,7 @@ namespace Ryujinx.HLE
{
internal IAalOutput AudioOut { get; private set; }
internal DeviceMemory Memory { get; private set; }
internal MemoryBlock Memory { get; private set; }
internal NvGpu Gpu { get; private set; }
@ -49,7 +50,7 @@ namespace Ryujinx.HLE
AudioOut = audioOut;
Memory = new DeviceMemory();
Memory = new MemoryBlock(1UL << 32);
Gpu = new NvGpu(renderer);

View file

@ -0,0 +1,274 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using static Ryujinx.Memory.MemoryConstants;
namespace Ryujinx.Memory
{
/// <summary>
/// Represents a block of contiguous physical guest memory.
/// </summary>
public sealed class MemoryBlock : IDisposable
{
private IntPtr _pointer;
private readonly HashSet<MemoryRange>[] _pages;
/// <summary>
/// Pointer to the memory block data.
/// </summary>
public IntPtr Pointer => _pointer;
/// <summary>
/// Size of the memory block.
/// </summary>
public ulong Size { get; }
/// <summary>
/// Initializes a new instance of the memory block class.
/// </summary>
/// <param name="size">Size of the memory block</param>
public MemoryBlock(ulong size)
{
_pointer = MemoryManagement.Allocate(size);
Size = size;
_pages = new HashSet<MemoryRange>[size >> PageBits];
}
/// <summary>
/// Reads bytes from the memory block.
/// </summary>
/// <param name="address">Starting address of the range being read</param>
/// <param name="data">Span where the bytes being read will be copied to</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Read(ulong address, Span<byte> data)
{
GetSpan(address, data.Length).CopyTo(data);
}
/// <summary>
/// Reads data from the memory block.
/// </summary>
/// <typeparam name="T">Type of the data</typeparam>
/// <param name="address">Address where the data is located</param>
/// <returns>Data at the specified address</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Read<T>(ulong address)
{
return GetRef<T>(address, Unsafe.SizeOf<T>());
}
/// <summary>
/// Writes bytes to the memory block.
/// </summary>
/// <param name="address">Starting address of the range being written</param>
/// <param name="data">Span where the bytes being written will be copied from</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ulong address, ReadOnlySpan<byte> data)
{
data.CopyTo(GetSpan(address, data.Length));
}
/// <summary>
/// Writes data to the memory block.
/// </summary>
/// <typeparam name="T">Type of the data being written</typeparam>
/// <param name="address">Address to write the data into</param>
/// <param name="data">Data to be written</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write<T>(ulong address, T data)
{
GetRef<T>(address, Unsafe.SizeOf<T>()) = data;
}
/// <summary>
/// Copies data from one memory location to another.
/// </summary>
/// <param name="srcAddress">Source address to read the data from</param>
/// <param name="dstAddress">Destination address to write the data into</param>
/// <param name="size">Size of the copy in bytes</param>
public void Copy(ulong srcAddress, ulong dstAddress, ulong size)
{
Write(dstAddress, GetSpan(srcAddress, (int)size));
}
/// <summary>
/// Fills a region of memory with zeros.
/// </summary>
/// <param name="address">Address of the region to fill with zeros</param>
/// <param name="size">Size in bytes of the region to fill</param>
public void ZeroFill(ulong address, ulong size)
{
GetSpan(address, (int)size).Fill(0);
}
/// <summary>
/// Creates a range of memory that is tracked for memory modification.
/// The range can be checked for modification of any sub-range inside the range.
/// </summary>
/// <param name="address">Starting address of the range being tracked</param>
/// <param name="size">Size in bytes of the range being tracked</param>
/// <returns>The new write tracked memory range</returns>
public MemoryRange CreateMemoryRange(ulong address, ulong size)
{
return new MemoryRange(this, address, size);
}
/// <summary>
/// Gets a reference of the data at a given memory block region.
/// </summary>
/// <typeparam name="T">Data type</typeparam>
/// <param name="address">Address of the memory region</param>
/// <param name="size">Size in bytes of the memory region</param>
/// <returns>A reference to the given memory region data</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe ref T GetRef<T>(ulong address, int size)
{
IntPtr ptr = _pointer;
if (ptr == IntPtr.Zero)
{
throw new ObjectDisposedException(nameof(MemoryBlock));
}
ulong endAddress = address + (ulong)size;
if (endAddress > Size || endAddress < address)
{
throw new ArgumentOutOfRangeException(nameof(size));
}
return ref Unsafe.AsRef<T>((void*)PtrAddr(ptr, address));
}
/// <summary>
/// Gets the span of a given memory block region.
/// </summary>
/// <param name="address">Start address of the memory region</param>
/// <param name="size">Size in bytes of the region</param>
/// <returns>Span of the memory region</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe Span<byte> GetSpan(ulong address, int size)
{
IntPtr ptr = _pointer;
if (ptr == IntPtr.Zero)
{
throw new ObjectDisposedException(nameof(MemoryBlock));
}
ulong endAddress = address + (ulong)size;
if (endAddress > Size || endAddress < address)
{
throw new ArgumentOutOfRangeException(nameof(size));
}
return new Span<byte>((void*)PtrAddr(ptr, address), size);
}
/// <summary>
/// Checks if a range of pages was modified, since the last call to this method.
/// </summary>
/// <param name="page">Page number of the first page to be checked</param>
/// <param name="count">Number of pages to check</param>
/// <param name="range">Memory range being checked</param>
/// <returns>True if any of the pages were modified, false otherwise</returns>
internal bool QueryModified(int page, int count, MemoryRange range)
{
return QueryModifiedImpl(page, count, range, null);
}
/// <summary>
/// Checks if a range of pages was modified, since the last call to this method.
/// </summary>
/// <param name="page">Page number of the first page to be checked</param>
/// <param name="count">Number of pages to check</param>
/// <param name="range">Memory range being checked</param>
/// <param name="outBuffer">Optional output buffer to write the new data</param>
/// <returns>True if any of the pages were modified, false otherwise</returns>
internal bool QueryModified(int page, int count, MemoryRange range, Span<byte> outBuffer)
{
return QueryModifiedImpl(page, count, range, outBuffer);
}
/// <summary>
/// Checks if a range of pages was modified, since the last call to this method.
/// </summary>
/// <param name="page">Page number of the first page to be checked</param>
/// <param name="count">Number of pages to check</param>
/// <param name="range">Memory range being checked</param>
/// <param name="outBuffer">Optional output buffer to write the new data</param>
/// <returns>True if any of the pages were modified, false otherwise</returns>
private bool QueryModifiedImpl(int page, int count, MemoryRange range, Span<byte> outBuffer)
{
if (count <= 0)
{
return false;
}
Span<IntPtr> addresses = stackalloc IntPtr[count];
IntPtr pagePtr = PtrAddr(_pointer, (ulong)page << PageBits);
MemoryManagement.QueryModifiedPages(pagePtr, (IntPtr)((ulong)count << PageBits), addresses, out ulong pc);
for (int i = 0; i < (int)pc; i++)
{
int modifiedPage = (int)((ulong)(addresses[i].ToInt64() - _pointer.ToInt64()) >> PageBits);
if (outBuffer != null)
{
Read((ulong)modifiedPage << PageBits, outBuffer.Slice((modifiedPage - page) << PageBits, PageSize));
}
_pages[modifiedPage]?.Clear();
}
bool modified = false;
for (int i = 0; i < count; i++)
{
if (_pages[page + i] == null)
{
_pages[page + i] = new HashSet<MemoryRange>();
}
modified |= _pages[page + i].Add(range);
}
return modified;
}
/// <summary>
/// Adds a 64-bits offset to a native pointer.
/// </summary>
/// <param name="pointer">Native pointer</param>
/// <param name="offset">Offset to add</param>
/// <returns>Native pointer with the added offset</returns>
private IntPtr PtrAddr(IntPtr pointer, ulong offset)
{
return (IntPtr)(pointer.ToInt64() + (long)offset);
}
/// <summary>
/// Frees the memory allocated for this memory block.
/// </summary>
/// <remarks>
/// It's an error to use the memory block after disposal.
/// </remarks>
public void Dispose()
{
IntPtr ptr = Interlocked.Exchange(ref _pointer, IntPtr.Zero);
if (ptr != IntPtr.Zero)
{
MemoryManagement.Free(ptr);
}
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Memory
{
static class MemoryConstants
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
public const int PageMask = PageSize - 1;
}
}

View file

@ -0,0 +1,63 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Memory
{
public static class MemoryManagement
{
public static IntPtr Allocate(ulong size)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
IntPtr sizeNint = new IntPtr((long)size);
return MemoryManagementWin.Allocate(sizeNint);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return MemoryManagementUnix.Allocate(size);
}
else
{
throw new PlatformNotSupportedException();
}
}
public static bool Free(IntPtr address)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return MemoryManagementWin.Free(address);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return MemoryManagementUnix.Free(address);
}
else
{
throw new PlatformNotSupportedException();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool QueryModifiedPages(IntPtr address, IntPtr size, Span<IntPtr> addresses, out ulong count)
{
// This is only supported on windows, but returning
// false (failed) is also valid for platforms without
// write tracking support on the OS.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return MemoryManagementWin.QueryModifiedPages(address, size, addresses, out count);
}
else
{
count = 0;
return false;
}
}
}
}

View file

@ -0,0 +1,49 @@
using Mono.Unix.Native;
using System;
namespace Ryujinx.Memory
{
static class MemoryManagementUnix
{
public static IntPtr Allocate(ulong size)
{
ulong pageSize = (ulong)Syscall.sysconf(SysconfName._SC_PAGESIZE);
const MmapProts prot = MmapProts.PROT_READ | MmapProts.PROT_WRITE;
const MmapFlags flags = MmapFlags.MAP_PRIVATE | MmapFlags.MAP_ANONYMOUS;
IntPtr ptr = Syscall.mmap(IntPtr.Zero, size + pageSize, prot, flags, -1, 0);
if (ptr == IntPtr.Zero)
{
throw new OutOfMemoryException();
}
unsafe
{
ptr = new IntPtr(ptr.ToInt64() + (long)pageSize);
*((ulong*)ptr - 1) = size;
}
return ptr;
}
public static bool Free(IntPtr address)
{
ulong pageSize = (ulong)Syscall.sysconf(SysconfName._SC_PAGESIZE);
ulong size;
unsafe
{
size = *((ulong*)address - 1);
address = new IntPtr(address.ToInt64() - (long)pageSize);
}
return Syscall.munmap(address, size + pageSize) == 0;
}
}
}

View file

@ -0,0 +1,112 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Memory
{
static class MemoryManagementWin
{
[Flags]
private enum AllocationType : uint
{
Commit = 0x1000,
Reserve = 0x2000,
Decommit = 0x4000,
Release = 0x8000,
Reset = 0x80000,
Physical = 0x400000,
TopDown = 0x100000,
WriteWatch = 0x200000,
LargePages = 0x20000000
}
[Flags]
private enum MemoryProtection : uint
{
NoAccess = 0x01,
ReadOnly = 0x02,
ReadWrite = 0x04,
WriteCopy = 0x08,
Execute = 0x10,
ExecuteRead = 0x20,
ExecuteReadWrite = 0x40,
ExecuteWriteCopy = 0x80,
GuardModifierflag = 0x100,
NoCacheModifierflag = 0x200,
WriteCombineModifierflag = 0x400
}
private enum WriteWatchFlags : uint
{
None = 0,
Reset = 1
}
[DllImport("kernel32.dll")]
private static extern IntPtr VirtualAlloc(
IntPtr lpAddress,
IntPtr dwSize,
AllocationType flAllocationType,
MemoryProtection flProtect);
[DllImport("kernel32.dll")]
private static extern bool VirtualFree(
IntPtr lpAddress,
IntPtr dwSize,
AllocationType dwFreeType);
[DllImport("kernel32.dll")]
private unsafe static extern int GetWriteWatch(
WriteWatchFlags dwFlags,
IntPtr lpBaseAddress,
IntPtr dwRegionSize,
IntPtr* lpAddresses,
ref ulong lpdwCount,
out uint lpdwGranularity);
public static IntPtr Allocate(IntPtr size)
{
const AllocationType flags =
AllocationType.Reserve |
AllocationType.Commit |
AllocationType.WriteWatch;
IntPtr ptr = VirtualAlloc(IntPtr.Zero, size, flags, MemoryProtection.ReadWrite);
if (ptr == IntPtr.Zero)
{
throw new OutOfMemoryException();
}
return ptr;
}
public static bool Free(IntPtr address)
{
return VirtualFree(address, IntPtr.Zero, AllocationType.Release);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe static bool QueryModifiedPages(IntPtr address, IntPtr size, Span<IntPtr> addresses, out ulong count)
{
ulong pagesCount = (ulong)addresses.Length;
int result;
fixed (IntPtr* addressesPtr = addresses)
{
result = GetWriteWatch(
WriteWatchFlags.Reset,
address,
size,
addressesPtr,
ref pagesCount,
out _);
}
count = pagesCount;
return result == 0;
}
}
}

View file

@ -0,0 +1,277 @@
using Ryujinx.Common;
using System;
using static Ryujinx.Memory.MemoryConstants;
namespace Ryujinx.Memory
{
/// <summary>
/// Represents a write tracked range of memory.
/// </summary>
public class MemoryRange
{
private readonly MemoryBlock _memoryBlock;
/// <summary>
/// Address of the memory range.
/// </summary>
public ulong Address { get; }
/// <summary>
/// Size in bytes of the memory range.
/// </summary>
public ulong Size { get; }
private readonly ulong _lastPageAddress;
private readonly int _firstPage;
private readonly int _firstPageOffset;
private readonly int _lastPage;
private readonly int _lastPageEndOffset;
private readonly byte[] _firstPageData;
private readonly byte[] _lastPageData;
/// <summary>
/// Creates a new instance of the memory range class.
/// </summary>
/// <param name="memoryBlock">Parent memory block that the range belongs to</param>
/// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes of the range</param>
internal MemoryRange(MemoryBlock memoryBlock, ulong address, ulong size)
{
_memoryBlock = memoryBlock;
Address = address;
Size = size;
int firstPage = (int)(address / PageSize);
int firstPageOffset = (int)(address & PageMask);
if (firstPageOffset != 0 || size < PageSize)
{
int dataSize = PageSize - firstPageOffset;
if ((ulong)dataSize > size)
{
dataSize = (int)size;
}
_firstPageData = new byte[dataSize];
_memoryBlock.Read(address, _firstPageData);
}
ulong endAddress = address + size;
int lastPage = (int)(BitUtils.DivRoundUp(endAddress, PageSize) - 1);
int lastPageEndOffset = (int)(endAddress & PageMask);
ulong lastPageAddress = endAddress - (ulong)lastPageEndOffset;
if (lastPage > firstPage && lastPageEndOffset != 0)
{
_lastPageData = new byte[lastPageEndOffset];
_memoryBlock.Read(lastPageAddress, _lastPageData);
}
ulong addressAligned = BitUtils.AlignUp(address, PageSize);
int alignedPagesCount = (int)((BitUtils.AlignDown(endAddress, PageSize) - addressAligned) >> PageBits);
memoryBlock.QueryModified((int)(addressAligned >> PageBits), alignedPagesCount, this);
_lastPageAddress = lastPageAddress;
_firstPage = firstPage;
_firstPageOffset = firstPageOffset;
_lastPage = lastPage;
_lastPageEndOffset = lastPageEndOffset;
}
/// <summary>
/// Checks if the memory range was written since the last call to this method.
/// </summary>
/// <returns>True if the range data was modified since the last call, false otherwise</returns>
public bool QueryModified()
{
bool modified = false;
int page = _firstPage;
if (_firstPageOffset != 0 || Size < PageSize)
{
int dataSize = _firstPageData.Length;
if ((ulong)dataSize > Size)
{
dataSize = (int)Size;
}
Span<byte> oldData = new Span<byte>(_firstPageData);
ReadOnlySpan<byte> newData = _memoryBlock.GetSpan(Address, dataSize);
if (!oldData.SequenceEqual(newData))
{
modified = true;
newData.CopyTo(oldData);
}
page++;
}
int endPage = _lastPageEndOffset != 0 ? _lastPage : _lastPage + 1;
int alignedPagesCount = endPage - page;
if (alignedPagesCount > 0 && _memoryBlock.QueryModified(page, alignedPagesCount, this))
{
modified = true;
}
if (_lastPage > _firstPage && _lastPageEndOffset != 0)
{
Span<byte> oldData = new Span<byte>(_lastPageData);
ReadOnlySpan<byte> newData = _memoryBlock.GetSpan(_lastPageAddress, _lastPageData.Length);
if (!oldData.SequenceEqual(newData))
{
modified = true;
newData.CopyTo(oldData);
}
}
return modified;
}
/// <summary>
/// Gets a span of data inside this memory range.
/// </summary>
/// <remarks>
/// Use this method after checking for data modification inside the memory range.
/// This ensures that you're working with the same data being used internally
/// for modification checks with memory comparison.
/// </remarks>
/// <returns>The data span</returns>
public Span<byte> GetSpan()
{
if (_firstPageData == null && _lastPageData == null)
{
return _memoryBlock.GetSpan(Address, (int)Size);
}
Span<byte> data = new byte[Size];
int middleOffset = 0;
int unalignedSize = 0;
if (_firstPageData != null)
{
middleOffset = _firstPageData.Length;
unalignedSize = _firstPageData.Length;
_firstPageData.CopyTo(data.Slice(0, _firstPageData.Length));
}
if (_lastPageData != null)
{
unalignedSize += _lastPageData.Length;
_lastPageData.CopyTo(data.Slice(data.Length - _lastPageData.Length));
}
int middleSize = data.Length - unalignedSize;
_memoryBlock.Read(Address + (ulong)middleOffset, data.Slice(middleOffset, middleSize));
return data;
}
/// <summary>
/// Checks if a given sub-range inside the memory range was written since the last
/// call to this method.
/// </summary>
/// <param name="offset">Offset of the sub-range being checked</param>
/// <param name="size">Size in bytes of the sub-range being checked</param>
/// <param name="data">Buffer used for memory comparison, and to hold the updated data</param>
/// <returns>True if the sub-range was modified, false otherwise</returns>
public bool QueryModified(int offset, int size, Span<byte> data)
{
ulong address = Address + (ulong)offset;
int firstPage = (int)(address / PageSize);
int firstPageOffset = (int)(address & PageMask);
ulong endAddress = address + (ulong)size;
int lastPage = (int)(BitUtils.DivRoundUp(endAddress, PageSize) - 1);
int lastPageEndOffset = (int)(endAddress & PageMask);
bool modified = false;
int page = firstPage;
int alignedOffset = offset;
if (firstPageOffset != 0 || size < PageSize)
{
int dataSize = PageSize - firstPageOffset;
if (dataSize > size)
{
dataSize = size;
}
alignedOffset += dataSize;
Span<byte> oldData = data.Slice(offset, dataSize);
ReadOnlySpan<byte> newData = _memoryBlock.GetSpan(address, dataSize);
if (!oldData.SequenceEqual(newData))
{
modified = true;
newData.CopyTo(oldData);
}
page++;
}
int endPage = lastPageEndOffset != 0 ? lastPage : lastPage + 1;
int alignedPagesCount = endPage - page;
if (alignedPagesCount > 0)
{
Span<byte> outBuffer = data.Slice(alignedOffset, alignedPagesCount << PageBits);
if (_memoryBlock.QueryModified(page, alignedPagesCount, this, outBuffer))
{
modified = true;
}
}
if (lastPage > firstPage && lastPageEndOffset != 0)
{
Span<byte> oldData = data.Slice(offset + size - lastPageEndOffset, lastPageEndOffset);
ReadOnlySpan<byte> newData = _memoryBlock.GetSpan(endAddress - (ulong)lastPageEndOffset, lastPageEndOffset);
if (!oldData.SequenceEqual(newData))
{
modified = true;
newData.CopyTo(oldData);
}
}
return modified;
}
}
}

View file

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
</ItemGroup>
</Project>

View file

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.8
# Visual Studio Version 16
VisualStudioVersion = 16.0.29613.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "Ryujinx\Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}"
EndProject
@ -28,6 +28,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Profiler", "Ryujinx
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ARMeilleure", "ARMeilleure\ARMeilleure.csproj", "{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Memory", "Ryujinx.Memory\Ryujinx.Memory.csproj", "{A5E6C691-9E22-4263-8F40-42F002CE66BE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -124,6 +126,14 @@ Global
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Profile Release|Any CPU.Build.0 = Release|Any CPU
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.Build.0 = Release|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Profile Release|Any CPU.ActiveCfg = Release|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Profile Release|Any CPU.Build.0 = Release|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE