mirror of
https://git.ryujinx.app/projects/Kenji-NX.git
synced 2026-10-02 11:49:02 +02:00
Android changes
This commit is contained in:
@@ -5,6 +5,8 @@ namespace Ryujinx.Cpu
|
||||
{
|
||||
public class AddressSpace : IDisposable
|
||||
{
|
||||
private const MemoryAllocationFlags AsFlags = MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible;
|
||||
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
|
||||
public MemoryBlock Base { get; }
|
||||
@@ -25,28 +27,42 @@ namespace Ryujinx.Cpu
|
||||
{
|
||||
addressSpace = null;
|
||||
|
||||
const MemoryAllocationFlags AsFlags = MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible;
|
||||
MemoryBlock baseMemory = null;
|
||||
MemoryBlock mirrorMemory = null;
|
||||
|
||||
try
|
||||
{
|
||||
baseMemory = new MemoryBlock(asSize, AsFlags);
|
||||
mirrorMemory = new MemoryBlock(asSize, AsFlags);
|
||||
addressSpace = new AddressSpace(backingMemory, baseMemory, mirrorMemory, asSize);
|
||||
}
|
||||
catch (SystemException)
|
||||
{
|
||||
baseMemory?.Dispose();
|
||||
mirrorMemory?.Dispose();
|
||||
}
|
||||
|
||||
return addressSpace != null;
|
||||
}
|
||||
|
||||
public static bool TryCreateWithoutMirror(ulong asSize, out MemoryBlock addressSpace)
|
||||
{
|
||||
addressSpace = null;
|
||||
|
||||
ulong minAddressSpaceSize = Math.Min(asSize, 1UL << 36);
|
||||
|
||||
// Attempt to create the address space with expected size or try to reduce it until it succeed.
|
||||
for (ulong addressSpaceSize = asSize; addressSpaceSize >= minAddressSpaceSize; addressSpaceSize >>= 1)
|
||||
for (ulong addressSpaceSize = asSize; addressSpaceSize >= minAddressSpaceSize; addressSpaceSize -= 0x100000000UL)
|
||||
{
|
||||
MemoryBlock baseMemory = null;
|
||||
MemoryBlock mirrorMemory = null;
|
||||
|
||||
try
|
||||
{
|
||||
baseMemory = new MemoryBlock(addressSpaceSize, AsFlags);
|
||||
mirrorMemory = new MemoryBlock(addressSpaceSize, AsFlags);
|
||||
addressSpace = new AddressSpace(backingMemory, baseMemory, mirrorMemory, addressSpaceSize);
|
||||
MemoryBlock baseMemory = new MemoryBlock(addressSpaceSize, AsFlags);
|
||||
addressSpace = baseMemory;
|
||||
|
||||
break;
|
||||
}
|
||||
catch (SystemException)
|
||||
{
|
||||
baseMemory?.Dispose();
|
||||
mirrorMemory?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ namespace ARMeilleure.Common
|
||||
public static AddressTable<TEntry> CreateForArm(bool for64Bits, MemoryManagerType type)
|
||||
{
|
||||
// Assume software memory means that we don't want to use any signal handlers.
|
||||
bool sparse = type != MemoryManagerType.SoftwareMmu && type != MemoryManagerType.SoftwarePageTable;
|
||||
bool sparse = type != MemoryManagerType.SoftwareMmu && type != MemoryManagerType.SoftwarePageTable && PlatformInfo.IsBionic != true;
|
||||
|
||||
return new AddressTable<TEntry>(AddressTablePresets.GetArmPreset(for64Bits, sparse), sparse);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
|
||||
/// </summary>
|
||||
public sealed class MemoryManagerHostNoMirror : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
{
|
||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||
private readonly bool _unsafeMode;
|
||||
|
||||
private readonly MemoryBlock _addressSpace;
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly PageTable<ulong> _pageTable;
|
||||
|
||||
public int AddressSpaceBits { get; }
|
||||
protected override ulong AddressSpaceSize { get; }
|
||||
|
||||
private readonly MemoryEhMeilleure _memoryEh;
|
||||
|
||||
private readonly ManagedPageFlags _pages;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool UsesPrivateAllocations => false;
|
||||
|
||||
public IntPtr PageTablePointer => _addressSpace.Pointer;
|
||||
|
||||
public MemoryManagerType Type => _unsafeMode ? MemoryManagerType.HostMappedUnsafe : MemoryManagerType.HostMapped;
|
||||
|
||||
public MemoryTracking Tracking { get; }
|
||||
|
||||
public event Action<ulong, ulong> UnmapEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the host mapped memory manager.
|
||||
/// </summary>
|
||||
/// <param name="addressSpace">Address space instance to use</param>
|
||||
/// <param name="unsafeMode">True if unmanaged access should not be masked (unsafe), false otherwise.</param>
|
||||
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
|
||||
public MemoryManagerHostNoMirror(
|
||||
MemoryBlock addressSpace,
|
||||
MemoryBlock backingMemory,
|
||||
bool unsafeMode,
|
||||
InvalidAccessHandler invalidAccessHandler)
|
||||
{
|
||||
_addressSpace = addressSpace;
|
||||
_backingMemory = backingMemory;
|
||||
_pageTable = new PageTable<ulong>();
|
||||
_invalidAccessHandler = invalidAccessHandler;
|
||||
_unsafeMode = unsafeMode;
|
||||
AddressSpaceSize = addressSpace.Size;
|
||||
|
||||
ulong asSize = PageSize;
|
||||
int asBits = PageBits;
|
||||
|
||||
while (asSize < addressSpace.Size)
|
||||
{
|
||||
asSize <<= 1;
|
||||
asBits++;
|
||||
}
|
||||
|
||||
AddressSpaceBits = asBits;
|
||||
|
||||
_pages = new ManagedPageFlags(asBits);
|
||||
|
||||
Tracking = new MemoryTracking(this, (int)MemoryBlock.GetPageSize(), invalidAccessHandler);
|
||||
_memoryEh = new MemoryEhMeilleure(addressSpace, null, Tracking);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the combination of virtual address and size is part of the addressable space and fully mapped.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
private void AssertMapped(ulong va, ulong size)
|
||||
{
|
||||
if (!ValidateAddressAndSize(va, size) || !_pages.IsRangeMapped(va, size))
|
||||
{
|
||||
throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
_addressSpace.MapView(_backingMemory, pa, va, size);
|
||||
_pages.AddMapping(va, size);
|
||||
PtMap(va, pa, size);
|
||||
|
||||
Tracking.Map(va, size);
|
||||
}
|
||||
|
||||
private void PtMap(ulong va, ulong pa, ulong size)
|
||||
{
|
||||
while (size != 0)
|
||||
{
|
||||
_pageTable.Map(va, pa);
|
||||
|
||||
va += PageSize;
|
||||
pa += PageSize;
|
||||
size -= PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
UnmapEvent?.Invoke(va, size);
|
||||
Tracking.Unmap(va, size);
|
||||
|
||||
_pages.RemoveMapping(va, size);
|
||||
PtUnmap(va, size);
|
||||
_addressSpace.UnmapView(_backingMemory, va, size);
|
||||
}
|
||||
|
||||
private void PtUnmap(ulong va, ulong size)
|
||||
{
|
||||
while (size != 0)
|
||||
{
|
||||
_pageTable.Unmap(va);
|
||||
|
||||
va += PageSize;
|
||||
size -= PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Reprotect(ulong va, ulong size, MemoryPermission permission)
|
||||
{
|
||||
}
|
||||
|
||||
public ref T GetRef<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
if (!IsContiguous(va, Unsafe.SizeOf<T>()))
|
||||
{
|
||||
ThrowMemoryNotContiguous();
|
||||
}
|
||||
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
|
||||
|
||||
return ref _backingMemory.GetRef<T>(GetPhysicalAddressChecked(va));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool IsMapped(ulong va)
|
||||
{
|
||||
return ValidateAddress(va) && _pages.IsMapped(va);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsRangeMapped(ulong va, ulong size)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
return _pages.IsRangeMapped(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return Enumerable.Empty<HostMemoryRange>();
|
||||
}
|
||||
|
||||
var guestRegions = GetPhysicalRegionsImpl(va, size);
|
||||
if (guestRegions == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var regions = new HostMemoryRange[guestRegions.Count];
|
||||
|
||||
for (int i = 0; i < regions.Length; i++)
|
||||
{
|
||||
var guestRegion = guestRegions[i];
|
||||
IntPtr pointer = _backingMemory.GetPointer(guestRegion.Address, guestRegion.Size);
|
||||
regions[i] = new HostMemoryRange((nuint)(ulong)pointer, guestRegion.Size);
|
||||
}
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return Enumerable.Empty<MemoryRange>();
|
||||
}
|
||||
|
||||
return GetPhysicalRegionsImpl(va, size);
|
||||
}
|
||||
|
||||
private List<MemoryRange> GetPhysicalRegionsImpl(ulong va, ulong size)
|
||||
{
|
||||
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int pages = GetPagesCount(va, (uint)size, out va);
|
||||
|
||||
var regions = new List<MemoryRange>();
|
||||
|
||||
ulong regionStart = GetPhysicalAddressInternal(va);
|
||||
ulong regionSize = PageSize;
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
if (!ValidateAddress(va + PageSize))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ulong newPa = GetPhysicalAddressInternal(va + PageSize);
|
||||
|
||||
if (GetPhysicalAddressInternal(va) + PageSize != newPa)
|
||||
{
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
regionStart = newPa;
|
||||
regionSize = 0;
|
||||
}
|
||||
|
||||
va += PageSize;
|
||||
regionSize += PageSize;
|
||||
}
|
||||
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
private ulong GetPhysicalAddressChecked(ulong va)
|
||||
{
|
||||
if (!IsMapped(va))
|
||||
{
|
||||
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}");
|
||||
}
|
||||
|
||||
return GetPhysicalAddressInternal(va);
|
||||
}
|
||||
|
||||
private ulong GetPhysicalAddressInternal(ulong va)
|
||||
{
|
||||
return _pageTable.Read(va) + (va & PageMask);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
|
||||
/// </remarks>
|
||||
public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
if (precise)
|
||||
{
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId);
|
||||
return;
|
||||
}
|
||||
|
||||
_pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest)
|
||||
{
|
||||
if (guest)
|
||||
{
|
||||
_addressSpace.Reprotect(va, size, protection, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pages.TrackingReprotect(va, size, protection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags)
|
||||
{
|
||||
return Tracking.BeginTracking(address, size, id, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags)
|
||||
{
|
||||
return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
|
||||
{
|
||||
return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of resources used by the memory manager.
|
||||
/// </summary>
|
||||
protected override void Destroy()
|
||||
{
|
||||
_addressSpace.Dispose();
|
||||
_memoryEh.Dispose();
|
||||
}
|
||||
|
||||
protected override Memory<byte> GetPhysicalAddressMemory(nuint pa, int size)
|
||||
=> _backingMemory.GetMemory(pa, size);
|
||||
|
||||
protected override Span<byte> GetPhysicalAddressSpan(nuint pa, int size)
|
||||
=> _backingMemory.GetSpan(pa, size);
|
||||
|
||||
protected override nuint TranslateVirtualAddressChecked(ulong va)
|
||||
=> (nuint)GetPhysicalAddressChecked(va);
|
||||
|
||||
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
|
||||
=> (nuint)GetPhysicalAddressInternal(va);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using ARMeilleure.Memory;
|
||||
using Humanizer;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -17,8 +15,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
private static readonly int _pageMask = _pageSize - 1;
|
||||
|
||||
private const int CodeAlignment = 4; // Bytes.
|
||||
private const int CacheSize = 256 * 1024 * 1024;
|
||||
private const int CacheSize = 2047 * 1024 * 1024;
|
||||
|
||||
private static ReservedRegion _jitRegion;
|
||||
private static JitCacheInvalidation _jitCacheInvalidator;
|
||||
|
||||
private static CacheMemoryAllocator _cacheAllocator;
|
||||
@@ -27,8 +26,6 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
|
||||
private static readonly Lock _lock = new();
|
||||
private static bool _initialized;
|
||||
private static readonly List<ReservedRegion> _jitRegions = [];
|
||||
private static int _activeRegionIndex = 0;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||
@@ -48,9 +45,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
return;
|
||||
}
|
||||
|
||||
var firstRegion = new ReservedRegion(allocator, CacheSize);
|
||||
_jitRegions.Add(firstRegion);
|
||||
_activeRegionIndex = 0;
|
||||
_jitRegion = new ReservedRegion(allocator, CacheSize);
|
||||
|
||||
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
|
||||
{
|
||||
@@ -70,8 +65,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
int funcOffset = Allocate(code.Length);
|
||||
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
|
||||
IntPtr funcPtr = targetRegion.Pointer + funcOffset;
|
||||
|
||||
IntPtr funcPtr = _jitRegion.Pointer + funcOffset;
|
||||
|
||||
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
@@ -85,9 +80,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
}
|
||||
else
|
||||
{
|
||||
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
|
||||
ReprotectAsWritable(funcOffset, code.Length);
|
||||
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
|
||||
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
||||
ReprotectAsExecutable(funcOffset, code.Length);
|
||||
|
||||
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
@@ -111,80 +106,50 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
foreach (var region in _jitRegions)
|
||||
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
|
||||
|
||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||
{
|
||||
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
|
||||
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
|
||||
|
||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||
{
|
||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
||||
_cacheEntries.RemoveAt(entryIndex);
|
||||
}
|
||||
|
||||
return;
|
||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
||||
_cacheEntries.RemoveAt(entryIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
|
||||
private static void ReprotectAsWritable(int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
|
||||
int regionStart = offset & ~_pageMask;
|
||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||
|
||||
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
|
||||
private static void ReprotectAsExecutable(int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
|
||||
int regionStart = offset & ~_pageMask;
|
||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||
|
||||
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static int Allocate(int codeSize)
|
||||
{
|
||||
codeSize = AlignCodeSize(codeSize);
|
||||
|
||||
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
|
||||
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||
|
||||
if (allocOffset < 0)
|
||||
{
|
||||
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||
|
||||
if (allocOffset >= 0)
|
||||
{
|
||||
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||
_activeRegionIndex = i;
|
||||
return allocOffset;
|
||||
}
|
||||
throw new OutOfMemoryException("JIT Cache exhausted.");
|
||||
}
|
||||
|
||||
int exhaustedRegion = _activeRegionIndex;
|
||||
var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
|
||||
_jitRegions.Add(newRegion);
|
||||
_activeRegionIndex = _jitRegions.Count - 1;
|
||||
|
||||
int newRegionNumber = _activeRegionIndex;
|
||||
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||
|
||||
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((long)(newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
|
||||
|
||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
||||
|
||||
int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
|
||||
if (allocOffsetNew < 0)
|
||||
{
|
||||
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
|
||||
}
|
||||
|
||||
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
|
||||
return allocOffsetNew;
|
||||
return allocOffset;
|
||||
}
|
||||
|
||||
private static int AlignCodeSize(int codeSize)
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
{
|
||||
private const int CodeAlignment = 4; // Bytes.
|
||||
private const int SharedCacheSize = 2047 * 1024 * 1024;
|
||||
private const int LocalCacheSize = 256 * 1024 * 1024;
|
||||
private const int LocalCacheSize = 128 * 1024 * 1024;
|
||||
|
||||
// How many calls to the same function we allow until we pad the shared cache to force the function to become available there
|
||||
// and allow the guest to take the fast path.
|
||||
|
||||
@@ -45,6 +45,11 @@ namespace Ryujinx.Cpu.LightningJit
|
||||
_translator.InvalidateJitCacheRegion(address, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||
{
|
||||
|
||||
@@ -55,6 +55,24 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
}
|
||||
|
||||
public MemoryEhMeilleure(ulong asSize, MemoryTracking tracking)
|
||||
{
|
||||
_tracking = tracking;
|
||||
_baseAddress = 0UL;
|
||||
ulong endAddress = asSize;
|
||||
|
||||
_trackingEvent = VirtualMemoryEvent;
|
||||
|
||||
_pageSize = MemoryBlock.GetPageSize();
|
||||
|
||||
bool added = NativeSignalHandler.AddTrackedRegion((nuint)_baseAddress, (nuint)endAddress, Marshal.GetFunctionPointerForDelegate(_trackingEvent));
|
||||
|
||||
if (!added)
|
||||
{
|
||||
throw new InvalidOperationException("Number of allowed tracked regions exceeded.");
|
||||
}
|
||||
}
|
||||
|
||||
private ulong VirtualMemoryEvent(ulong address, ulong size, bool write)
|
||||
{
|
||||
ulong pageSize = _pageSize;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Ryujinx.Cpu.Nce.Arm64
|
||||
{
|
||||
enum ArmCondition
|
||||
{
|
||||
Eq = 0,
|
||||
Ne = 1,
|
||||
GeUn = 2,
|
||||
LtUn = 3,
|
||||
Mi = 4,
|
||||
Pl = 5,
|
||||
Vs = 6,
|
||||
Vc = 7,
|
||||
GtUn = 8,
|
||||
LeUn = 9,
|
||||
Ge = 10,
|
||||
Lt = 11,
|
||||
Gt = 12,
|
||||
Le = 13,
|
||||
Al = 14,
|
||||
Nv = 15,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Ryujinx.Cpu.Nce.Arm64
|
||||
{
|
||||
enum ArmExtensionType
|
||||
{
|
||||
Uxtb = 0,
|
||||
Uxth = 1,
|
||||
Uxtw = 2,
|
||||
Uxtx = 3,
|
||||
Sxtb = 4,
|
||||
Sxth = 5,
|
||||
Sxtw = 6,
|
||||
Sxtx = 7,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
namespace Ryujinx.Cpu.Nce.Arm64
|
||||
{
|
||||
enum ArmShiftType
|
||||
{
|
||||
Lsl = 0,
|
||||
Lsr = 1,
|
||||
Asr = 2,
|
||||
Ror = 3,
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce.Arm64
|
||||
{
|
||||
static class CodeGenCommon
|
||||
{
|
||||
public static bool TryEncodeBitMask(Operand operand, out int immN, out int immS, out int immR)
|
||||
{
|
||||
return TryEncodeBitMask(operand.Type, operand.Value, out immN, out immS, out immR);
|
||||
}
|
||||
|
||||
public static bool TryEncodeBitMask(OperandType type, ulong value, out int immN, out int immS, out int immR)
|
||||
{
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
value |= value << 32;
|
||||
}
|
||||
|
||||
return TryEncodeBitMask(value, out immN, out immS, out immR);
|
||||
}
|
||||
|
||||
public static bool TryEncodeBitMask(ulong value, out int immN, out int immS, out int immR)
|
||||
{
|
||||
// Some special values also can't be encoded:
|
||||
// 0 can't be encoded because we need to subtract 1 from onesCount (which would became negative if 0).
|
||||
// A value with all bits set can't be encoded because it is reserved according to the spec, because:
|
||||
// Any value AND all ones will be equal itself, so it's effectively a no-op.
|
||||
// Any value OR all ones will be equal all ones, so one can just use MOV.
|
||||
// Any value XOR all ones will be equal its inverse, so one can just use MVN.
|
||||
if (value == 0 || value == ulong.MaxValue)
|
||||
{
|
||||
immN = 0;
|
||||
immS = 0;
|
||||
immR = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Normalize value, rotating it such that the LSB is 1: Ensures we get a complete element that has not
|
||||
// been cut-in-half across the word boundary.
|
||||
int rotation = BitOperations.TrailingZeroCount(value & (value + 1));
|
||||
ulong rotatedValue = ulong.RotateRight(value, rotation);
|
||||
|
||||
// Now that we have a complete element in the LSB with the LSB = 1, determine size and number of ones
|
||||
// in element.
|
||||
int elementSize = BitOperations.TrailingZeroCount(rotatedValue & (rotatedValue + 1));
|
||||
int onesInElement = BitOperations.TrailingZeroCount(~rotatedValue);
|
||||
|
||||
// Check the value is repeating; also ensures element size is a power of two.
|
||||
if (ulong.RotateRight(value, elementSize) != value)
|
||||
{
|
||||
immN = 0;
|
||||
immS = 0;
|
||||
immR = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
immN = (elementSize >> 6) & 1;
|
||||
immS = (((~elementSize + 1) << 1) | (onesInElement - 1)) & 0x3f;
|
||||
immR = (elementSize - rotation) & (elementSize - 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce.Arm64
|
||||
{
|
||||
struct Operand
|
||||
{
|
||||
public readonly OperandKind Kind { get; }
|
||||
public readonly OperandType Type { get; }
|
||||
public readonly ulong Value { get; }
|
||||
|
||||
public Operand(OperandKind kind, OperandType type, ulong value)
|
||||
{
|
||||
Kind = kind;
|
||||
Type = type;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public Operand(int index, RegisterType regType, OperandType type) : this(OperandKind.Register, type, (ulong)((int)regType << 24 | index))
|
||||
{
|
||||
}
|
||||
|
||||
public Operand(OperandType type, ulong value) : this(OperandKind.Constant, type, value)
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly Register GetRegister()
|
||||
{
|
||||
Debug.Assert(Kind == OperandKind.Register);
|
||||
|
||||
return new Register((int)Value & 0xffffff, (RegisterType)(Value >> 24));
|
||||
}
|
||||
|
||||
public readonly int AsInt32()
|
||||
{
|
||||
return (int)Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Ryujinx.Cpu.Nce.Arm64
|
||||
{
|
||||
enum OperandKind
|
||||
{
|
||||
None,
|
||||
Constant,
|
||||
Label,
|
||||
Register,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce.Arm64
|
||||
{
|
||||
enum OperandType
|
||||
{
|
||||
None,
|
||||
I32,
|
||||
I64,
|
||||
FP32,
|
||||
FP64,
|
||||
V128,
|
||||
}
|
||||
|
||||
static class OperandTypeExtensions
|
||||
{
|
||||
public static bool IsInteger(this OperandType type)
|
||||
{
|
||||
return type == OperandType.I32 ||
|
||||
type == OperandType.I64;
|
||||
}
|
||||
|
||||
public static int GetSizeInBytes(this OperandType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
OperandType.FP32 => 4,
|
||||
OperandType.FP64 => 8,
|
||||
OperandType.I32 => 4,
|
||||
OperandType.I64 => 8,
|
||||
OperandType.V128 => 16,
|
||||
_ => throw new InvalidOperationException($"Invalid operand type \"{type}\"."),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce.Arm64
|
||||
{
|
||||
readonly struct Register : IEquatable<Register>
|
||||
{
|
||||
public int Index { get; }
|
||||
|
||||
public RegisterType Type { get; }
|
||||
|
||||
public Register(int index, RegisterType type)
|
||||
{
|
||||
Index = index;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (ushort)Index | ((int)Type << 16);
|
||||
}
|
||||
|
||||
public static bool operator ==(Register x, Register y)
|
||||
{
|
||||
return x.Equals(y);
|
||||
}
|
||||
|
||||
public static bool operator !=(Register x, Register y)
|
||||
{
|
||||
return !x.Equals(y);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Register reg && Equals(reg);
|
||||
}
|
||||
|
||||
public bool Equals(Register other)
|
||||
{
|
||||
return other.Index == Index &&
|
||||
other.Type == Type;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce.Arm64
|
||||
{
|
||||
readonly struct RegisterSaveRestore
|
||||
{
|
||||
private const int FpRegister = 29;
|
||||
private const int LrRegister = 30;
|
||||
|
||||
private const int Encodable9BitsOffsetLimit = 0x200;
|
||||
|
||||
private readonly int _intMask;
|
||||
private readonly int _vecMask;
|
||||
private readonly OperandType _vecType;
|
||||
private readonly bool _hasCall;
|
||||
|
||||
public RegisterSaveRestore(int intMask, int vecMask = 0, OperandType vecType = OperandType.FP64, bool hasCall = false)
|
||||
{
|
||||
_intMask = intMask;
|
||||
_vecMask = vecMask;
|
||||
_vecType = vecType;
|
||||
_hasCall = hasCall;
|
||||
}
|
||||
|
||||
public void WritePrologue(Assembler asm)
|
||||
{
|
||||
int intMask = _intMask;
|
||||
int vecMask = _vecMask;
|
||||
|
||||
int intCalleeSavedRegsCount = BitOperations.PopCount((uint)intMask);
|
||||
int vecCalleeSavedRegsCount = BitOperations.PopCount((uint)vecMask);
|
||||
|
||||
int calleeSaveRegionSize = Align16(intCalleeSavedRegsCount * 8 + vecCalleeSavedRegsCount * _vecType.GetSizeInBytes());
|
||||
|
||||
int offset = 0;
|
||||
|
||||
WritePrologueCalleeSavesPreIndexed(asm, ref intMask, ref offset, calleeSaveRegionSize, OperandType.I64);
|
||||
|
||||
if (_vecType == OperandType.V128 && (intCalleeSavedRegsCount & 1) != 0)
|
||||
{
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
WritePrologueCalleeSavesPreIndexed(asm, ref vecMask, ref offset, calleeSaveRegionSize, _vecType);
|
||||
|
||||
if (_hasCall)
|
||||
{
|
||||
Operand rsp = Register(Assembler.SpRegister);
|
||||
|
||||
asm.StpRiPre(Register(FpRegister), Register(LrRegister), rsp, -16);
|
||||
asm.MovSp(Register(FpRegister), rsp);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WritePrologueCalleeSavesPreIndexed(
|
||||
Assembler asm,
|
||||
ref int mask,
|
||||
ref int offset,
|
||||
int calleeSaveRegionSize,
|
||||
OperandType type)
|
||||
{
|
||||
if ((BitOperations.PopCount((uint)mask) & 1) != 0)
|
||||
{
|
||||
int reg = BitOperations.TrailingZeroCount(mask);
|
||||
|
||||
mask &= ~(1 << reg);
|
||||
|
||||
if (offset != 0)
|
||||
{
|
||||
asm.StrRiUn(Register(reg, type), Register(Assembler.SpRegister), offset);
|
||||
}
|
||||
else if (calleeSaveRegionSize < Encodable9BitsOffsetLimit)
|
||||
{
|
||||
asm.StrRiPre(Register(reg, type), Register(Assembler.SpRegister), -calleeSaveRegionSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
asm.Sub(Register(Assembler.SpRegister), Register(Assembler.SpRegister), new Operand(OperandType.I64, (ulong)calleeSaveRegionSize));
|
||||
asm.StrRiUn(Register(reg, type), Register(Assembler.SpRegister), 0);
|
||||
}
|
||||
|
||||
offset += type.GetSizeInBytes();
|
||||
}
|
||||
|
||||
while (mask != 0)
|
||||
{
|
||||
int reg = BitOperations.TrailingZeroCount(mask);
|
||||
|
||||
mask &= ~(1 << reg);
|
||||
|
||||
int reg2 = BitOperations.TrailingZeroCount(mask);
|
||||
|
||||
mask &= ~(1 << reg2);
|
||||
|
||||
if (offset != 0)
|
||||
{
|
||||
asm.StpRiUn(Register(reg, type), Register(reg2, type), Register(Assembler.SpRegister), offset);
|
||||
}
|
||||
else if (calleeSaveRegionSize < Encodable9BitsOffsetLimit)
|
||||
{
|
||||
asm.StpRiPre(Register(reg, type), Register(reg2, type), Register(Assembler.SpRegister), -calleeSaveRegionSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
asm.Sub(Register(Assembler.SpRegister), Register(Assembler.SpRegister), new Operand(OperandType.I64, (ulong)calleeSaveRegionSize));
|
||||
asm.StpRiUn(Register(reg, type), Register(reg2, type), Register(Assembler.SpRegister), 0);
|
||||
}
|
||||
|
||||
offset += type.GetSizeInBytes() * 2;
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteEpilogue(Assembler asm)
|
||||
{
|
||||
int intMask = _intMask;
|
||||
int vecMask = _vecMask;
|
||||
|
||||
int intCalleeSavedRegsCount = BitOperations.PopCount((uint)intMask);
|
||||
int vecCalleeSavedRegsCount = BitOperations.PopCount((uint)vecMask);
|
||||
|
||||
bool misalignedVector = _vecType == OperandType.V128 && (intCalleeSavedRegsCount & 1) != 0;
|
||||
|
||||
int offset = intCalleeSavedRegsCount * 8 + vecCalleeSavedRegsCount * _vecType.GetSizeInBytes();
|
||||
|
||||
if (misalignedVector)
|
||||
{
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
int calleeSaveRegionSize = Align16(offset);
|
||||
|
||||
if (_hasCall)
|
||||
{
|
||||
Operand rsp = Register(Assembler.SpRegister);
|
||||
|
||||
asm.LdpRiPost(Register(FpRegister), Register(LrRegister), rsp, 16);
|
||||
}
|
||||
|
||||
WriteEpilogueCalleeSavesPostIndexed(asm, ref vecMask, ref offset, calleeSaveRegionSize, _vecType);
|
||||
|
||||
if (misalignedVector)
|
||||
{
|
||||
offset -= 8;
|
||||
}
|
||||
|
||||
WriteEpilogueCalleeSavesPostIndexed(asm, ref intMask, ref offset, calleeSaveRegionSize, OperandType.I64);
|
||||
}
|
||||
|
||||
private static void WriteEpilogueCalleeSavesPostIndexed(
|
||||
Assembler asm,
|
||||
ref int mask,
|
||||
ref int offset,
|
||||
int calleeSaveRegionSize,
|
||||
OperandType type)
|
||||
{
|
||||
while (mask != 0)
|
||||
{
|
||||
int reg = HighestBitSet(mask);
|
||||
|
||||
mask &= ~(1 << reg);
|
||||
|
||||
if (mask != 0)
|
||||
{
|
||||
int reg2 = HighestBitSet(mask);
|
||||
|
||||
mask &= ~(1 << reg2);
|
||||
|
||||
offset -= type.GetSizeInBytes() * 2;
|
||||
|
||||
if (offset != 0)
|
||||
{
|
||||
asm.LdpRiUn(Register(reg2, type), Register(reg, type), Register(Assembler.SpRegister), offset);
|
||||
}
|
||||
else if (calleeSaveRegionSize < Encodable9BitsOffsetLimit)
|
||||
{
|
||||
asm.LdpRiPost(Register(reg2, type), Register(reg, type), Register(Assembler.SpRegister), calleeSaveRegionSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
asm.LdpRiUn(Register(reg2, type), Register(reg, type), Register(Assembler.SpRegister), 0);
|
||||
asm.Add(Register(Assembler.SpRegister), Register(Assembler.SpRegister), new Operand(OperandType.I64, (ulong)calleeSaveRegionSize));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
offset -= type.GetSizeInBytes();
|
||||
|
||||
if (offset != 0)
|
||||
{
|
||||
asm.LdrRiUn(Register(reg, type), Register(Assembler.SpRegister), offset);
|
||||
}
|
||||
else if (calleeSaveRegionSize < Encodable9BitsOffsetLimit)
|
||||
{
|
||||
asm.LdrRiPost(Register(reg, type), Register(Assembler.SpRegister), calleeSaveRegionSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
asm.LdrRiUn(Register(reg, type), Register(Assembler.SpRegister), 0);
|
||||
asm.Add(Register(Assembler.SpRegister), Register(Assembler.SpRegister), new Operand(OperandType.I64, (ulong)calleeSaveRegionSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int HighestBitSet(int value)
|
||||
{
|
||||
return 31 - BitOperations.LeadingZeroCount((uint)value);
|
||||
}
|
||||
|
||||
private static Operand Register(int register, OperandType type = OperandType.I64)
|
||||
{
|
||||
return new Operand(register, RegisterType.Integer, type);
|
||||
}
|
||||
|
||||
private static int Align16(int value)
|
||||
{
|
||||
return (value + 0xf) & ~0xf;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Ryujinx.Cpu.Nce.Arm64
|
||||
{
|
||||
enum RegisterType
|
||||
{
|
||||
Integer,
|
||||
Vector,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
|
||||
/// </summary>
|
||||
public sealed class MemoryManagerNative : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
{
|
||||
private readonly MemoryBlock _addressSpace;
|
||||
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly PageTable<ulong> _pageTable;
|
||||
|
||||
private readonly MemoryEhMeilleure _memoryEh;
|
||||
|
||||
private readonly ManagedPageFlags _pages;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool UsesPrivateAllocations => false;
|
||||
|
||||
public IntPtr PageTablePointer => IntPtr.Zero;
|
||||
|
||||
public ulong ReservedSize => (ulong)_addressSpace.Pointer.ToInt64();
|
||||
|
||||
public MemoryManagerType Type => MemoryManagerType.HostMappedUnsafe;
|
||||
|
||||
public MemoryTracking Tracking { get; }
|
||||
|
||||
public event Action<ulong, ulong> UnmapEvent;
|
||||
|
||||
public int AddressSpaceBits { get; }
|
||||
protected override ulong AddressSpaceSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the host mapped memory manager.
|
||||
/// </summary>
|
||||
/// <param name="addressSpace">Address space memory block</param>
|
||||
/// <param name="backingMemory">Physical backing memory where virtual memory will be mapped to</param>
|
||||
/// <param name="addressSpaceSize">Size of the address space</param>
|
||||
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
|
||||
public MemoryManagerNative(
|
||||
MemoryBlock addressSpace,
|
||||
MemoryBlock backingMemory,
|
||||
ulong addressSpaceSize,
|
||||
InvalidAccessHandler invalidAccessHandler = null)
|
||||
{
|
||||
_backingMemory = backingMemory;
|
||||
_pageTable = new PageTable<ulong>();
|
||||
AddressSpaceSize = addressSpaceSize;
|
||||
|
||||
ulong asSize = PageSize;
|
||||
int asBits = PageBits;
|
||||
|
||||
while (asSize < addressSpaceSize)
|
||||
{
|
||||
asSize <<= 1;
|
||||
asBits++;
|
||||
}
|
||||
|
||||
AddressSpaceBits = asBits;
|
||||
|
||||
_pages = new ManagedPageFlags(asBits);
|
||||
|
||||
_addressSpace = addressSpace;
|
||||
|
||||
Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler);
|
||||
_memoryEh = new MemoryEhMeilleure(addressSpaceSize, Tracking);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
_addressSpace.MapView(_backingMemory, pa, AddressToOffset(va), size);
|
||||
_pages.AddMapping(va, size);
|
||||
PtMap(va, pa, size);
|
||||
|
||||
Tracking.Map(va, size);
|
||||
}
|
||||
|
||||
private void PtMap(ulong va, ulong pa, ulong size)
|
||||
{
|
||||
while (size != 0)
|
||||
{
|
||||
_pageTable.Map(va, pa);
|
||||
|
||||
va += PageSize;
|
||||
pa += PageSize;
|
||||
size -= PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
UnmapEvent?.Invoke(va, size);
|
||||
Tracking.Unmap(va, size);
|
||||
|
||||
_pages.RemoveMapping(va, size);
|
||||
PtUnmap(va, size);
|
||||
_addressSpace.UnmapView(_backingMemory, AddressToOffset(va), size);
|
||||
}
|
||||
|
||||
private void PtUnmap(ulong va, ulong size)
|
||||
{
|
||||
while (size != 0)
|
||||
{
|
||||
_pageTable.Unmap(va);
|
||||
|
||||
va += PageSize;
|
||||
size -= PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
|
||||
{
|
||||
_addressSpace.Reprotect(AddressToOffset(va), size, protection);
|
||||
}
|
||||
|
||||
public ref T GetRef<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
if (!IsContiguous(va, Unsafe.SizeOf<T>()))
|
||||
{
|
||||
ThrowMemoryNotContiguous();
|
||||
}
|
||||
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
|
||||
|
||||
return ref _backingMemory.GetRef<T>(GetPhysicalAddressChecked(va));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool IsMapped(ulong va)
|
||||
{
|
||||
return ValidateAddress(va) && _pages.IsMapped(va);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsRangeMapped(ulong va, ulong size)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
return _pages.IsRangeMapped(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return Enumerable.Empty<HostMemoryRange>();
|
||||
}
|
||||
|
||||
var guestRegions = GetPhysicalRegionsImpl(va, size);
|
||||
if (guestRegions == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var regions = new HostMemoryRange[guestRegions.Count];
|
||||
|
||||
for (int i = 0; i < regions.Length; i++)
|
||||
{
|
||||
var guestRegion = guestRegions[i];
|
||||
IntPtr pointer = _backingMemory.GetPointer(guestRegion.Address, guestRegion.Size);
|
||||
regions[i] = new HostMemoryRange((nuint)(ulong)pointer, guestRegion.Size);
|
||||
}
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return Enumerable.Empty<MemoryRange>();
|
||||
}
|
||||
|
||||
return GetPhysicalRegionsImpl(va, size);
|
||||
}
|
||||
|
||||
private List<MemoryRange> GetPhysicalRegionsImpl(ulong va, ulong size)
|
||||
{
|
||||
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int pages = GetPagesCount(va, (uint)size, out va);
|
||||
|
||||
var regions = new List<MemoryRange>();
|
||||
|
||||
ulong regionStart = GetPhysicalAddressInternal(va);
|
||||
ulong regionSize = PageSize;
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
if (!ValidateAddress(va + PageSize))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ulong newPa = GetPhysicalAddressInternal(va + PageSize);
|
||||
|
||||
if (GetPhysicalAddressInternal(va) + PageSize != newPa)
|
||||
{
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
regionStart = newPa;
|
||||
regionSize = 0;
|
||||
}
|
||||
|
||||
va += PageSize;
|
||||
regionSize += PageSize;
|
||||
}
|
||||
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
private ulong GetPhysicalAddressChecked(ulong va)
|
||||
{
|
||||
if (!IsMapped(va))
|
||||
{
|
||||
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}");
|
||||
}
|
||||
|
||||
return GetPhysicalAddressInternal(va);
|
||||
}
|
||||
|
||||
private ulong GetPhysicalAddressInternal(ulong va)
|
||||
{
|
||||
return _pageTable.Read(va) + (va & PageMask);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
|
||||
/// </remarks>
|
||||
public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
if (precise)
|
||||
{
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId);
|
||||
return;
|
||||
}
|
||||
|
||||
_pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest)
|
||||
{
|
||||
if (guest)
|
||||
{
|
||||
_addressSpace.Reprotect(AddressToOffset(va), size, protection, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pages.TrackingReprotect(va, size, protection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags)
|
||||
{
|
||||
return Tracking.BeginTracking(address, size, id, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags)
|
||||
{
|
||||
return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
|
||||
{
|
||||
return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
|
||||
}
|
||||
|
||||
private ulong AddressToOffset(ulong address)
|
||||
{
|
||||
if (address < ReservedSize)
|
||||
{
|
||||
throw new ArgumentException($"Invalid address 0x{address:x16}");
|
||||
}
|
||||
|
||||
return address - ReservedSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of resources used by the memory manager.
|
||||
/// </summary>
|
||||
protected override void Destroy()
|
||||
{
|
||||
_addressSpace.Dispose();
|
||||
_memoryEh.Dispose();
|
||||
}
|
||||
|
||||
protected override Memory<byte> GetPhysicalAddressMemory(nuint pa, int size)
|
||||
=> _backingMemory.GetMemory(pa, size);
|
||||
|
||||
protected override Span<byte> GetPhysicalAddressSpan(nuint pa, int size)
|
||||
=> _backingMemory.GetSpan(pa, size);
|
||||
|
||||
protected override nuint TranslateVirtualAddressChecked(ulong va)
|
||||
=> (nuint)GetPhysicalAddressChecked(va);
|
||||
|
||||
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
|
||||
=> (nuint)GetPhysicalAddressInternal(va);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Memory;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce
|
||||
{
|
||||
/// <summary>
|
||||
/// Native Code Execution CPU code patch.
|
||||
/// </summary>
|
||||
public class NceCpuCodePatch
|
||||
{
|
||||
private readonly List<uint> _code;
|
||||
|
||||
private readonly struct PatchTarget
|
||||
{
|
||||
public readonly int TextIndex;
|
||||
public readonly int PatchStartIndex;
|
||||
public readonly int PatchBranchIndex;
|
||||
|
||||
public PatchTarget(int textIndex, int patchStartIndex, int patchBranchIndex)
|
||||
{
|
||||
TextIndex = textIndex;
|
||||
PatchStartIndex = patchStartIndex;
|
||||
PatchBranchIndex = patchBranchIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<PatchTarget> _patchTargets;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong Size => BitUtils.AlignUp((ulong)_code.Count * sizeof(uint), 0x1000UL);
|
||||
|
||||
public NceCpuCodePatch()
|
||||
{
|
||||
_code = new();
|
||||
_patchTargets = new();
|
||||
}
|
||||
|
||||
internal void AddCode(int textIndex, IEnumerable<uint> code)
|
||||
{
|
||||
int patchStartIndex = _code.Count;
|
||||
_code.AddRange(code);
|
||||
_patchTargets.Add(new PatchTarget(textIndex, patchStartIndex, _code.Count - 1));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Write(IVirtualMemoryManager memoryManager, ulong patchAddress, ulong textAddress)
|
||||
{
|
||||
uint[] code = _code.ToArray();
|
||||
|
||||
foreach (var patchTarget in _patchTargets)
|
||||
{
|
||||
ulong instPatchStartAddress = patchAddress + (ulong)patchTarget.PatchStartIndex * sizeof(uint);
|
||||
ulong instPatchBranchAddress = patchAddress + (ulong)patchTarget.PatchBranchIndex * sizeof(uint);
|
||||
ulong instTextAddress = textAddress + (ulong)patchTarget.TextIndex * sizeof(uint);
|
||||
|
||||
uint prevInst = memoryManager.Read<uint>(instTextAddress);
|
||||
|
||||
code[patchTarget.PatchBranchIndex] |= EncodeSImm26_2(checked((int)((long)instTextAddress - (long)instPatchBranchAddress + sizeof(uint))));
|
||||
memoryManager.Write(instTextAddress, 0x14000000u | EncodeSImm26_2(checked((int)((long)instPatchStartAddress - (long)instTextAddress))));
|
||||
|
||||
uint newInst = memoryManager.Read<uint>(instTextAddress);
|
||||
}
|
||||
|
||||
if (Size != 0)
|
||||
{
|
||||
memoryManager.Write(patchAddress, MemoryMarshal.Cast<uint, byte>(code));
|
||||
memoryManager.Reprotect(patchAddress, Size, MemoryPermission.ReadAndExecute);
|
||||
}
|
||||
}
|
||||
|
||||
private static uint EncodeSImm26_2(int value)
|
||||
{
|
||||
uint imm = (uint)(value >> 2) & 0x3ffffff;
|
||||
Debug.Assert(((int)imm << 6) >> 4 == value, $"Failed to encode constant 0x{value:X}.");
|
||||
return imm;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Cpu.Signal;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce
|
||||
{
|
||||
class NceCpuContext : ICpuContext
|
||||
{
|
||||
private static uint[] _getTpidrEl0Code = new uint[]
|
||||
{
|
||||
GetMrsTpidrEl0(0), // mrs x0, tpidr_el0
|
||||
0xd65f03c0u, // ret
|
||||
};
|
||||
|
||||
private static uint GetMrsTpidrEl0(uint rd)
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
return 0xd53bd060u | rd; // TPIDRRO
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0xd53bd040u | rd; // TPIDR
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct CodeWriter
|
||||
{
|
||||
private readonly List<uint> _fullCode;
|
||||
|
||||
public CodeWriter()
|
||||
{
|
||||
_fullCode = new List<uint>();
|
||||
}
|
||||
|
||||
public ulong Write(uint[] code)
|
||||
{
|
||||
ulong offset = (ulong)_fullCode.Count * sizeof(uint);
|
||||
_fullCode.AddRange(code);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
public MemoryBlock CreateMemoryBlock()
|
||||
{
|
||||
ReadOnlySpan<byte> codeBytes = MemoryMarshal.Cast<uint, byte>(_fullCode.ToArray());
|
||||
|
||||
MemoryBlock codeBlock = new(BitUtils.AlignUp((ulong)codeBytes.Length, 0x1000UL));
|
||||
|
||||
codeBlock.Write(0, codeBytes);
|
||||
codeBlock.Reprotect(0, (ulong)codeBytes.Length, MemoryPermission.ReadAndExecute, true);
|
||||
|
||||
return codeBlock;
|
||||
}
|
||||
}
|
||||
|
||||
private delegate void ThreadStart(IntPtr nativeContextPtr);
|
||||
private delegate IntPtr GetTpidrEl0();
|
||||
private static MemoryBlock _codeBlock;
|
||||
private static ThreadStart _threadStart;
|
||||
private static GetTpidrEl0 _getTpidrEl0;
|
||||
|
||||
private readonly ITickSource _tickSource;
|
||||
private readonly IMemoryManager _memoryManager;
|
||||
|
||||
static NceCpuContext()
|
||||
{
|
||||
CodeWriter codeWriter = new();
|
||||
|
||||
uint[] threadStartCode = NcePatcher.GenerateThreadStartCode();
|
||||
uint[] ehSuspendCode = NcePatcher.GenerateSuspendExceptionHandler();
|
||||
|
||||
ulong threadStartCodeOffset = codeWriter.Write(threadStartCode);
|
||||
ulong getTpidrEl0CodeOffset = codeWriter.Write(_getTpidrEl0Code);
|
||||
ulong ehSuspendCodeOffset = codeWriter.Write(ehSuspendCode);
|
||||
|
||||
MemoryBlock codeBlock = null;
|
||||
|
||||
NativeSignalHandler.InitializeSignalHandler((IntPtr oldSignalHandlerSegfaultPtr, IntPtr signalHandlerPtr) =>
|
||||
{
|
||||
uint[] ehWrapperCode = NcePatcher.GenerateWrapperExceptionHandler(oldSignalHandlerSegfaultPtr, signalHandlerPtr);
|
||||
ulong ehWrapperCodeOffset = codeWriter.Write(ehWrapperCode);
|
||||
codeBlock = codeWriter.CreateMemoryBlock();
|
||||
return codeBlock.GetPointer(ehWrapperCodeOffset, (ulong)ehWrapperCode.Length * sizeof(uint));
|
||||
});
|
||||
|
||||
NativeSignalHandler.InstallUnixSignalHandler(NceThreadPal.UnixSuspendSignal, codeBlock.GetPointer(ehSuspendCodeOffset, (ulong)ehSuspendCode.Length * sizeof(uint)));
|
||||
|
||||
_threadStart = Marshal.GetDelegateForFunctionPointer<ThreadStart>(codeBlock.GetPointer(threadStartCodeOffset, (ulong)threadStartCode.Length * sizeof(uint)));
|
||||
_getTpidrEl0 = Marshal.GetDelegateForFunctionPointer<GetTpidrEl0>(codeBlock.GetPointer(getTpidrEl0CodeOffset, (ulong)_getTpidrEl0Code.Length * sizeof(uint)));
|
||||
_codeBlock = codeBlock;
|
||||
}
|
||||
|
||||
public NceCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
|
||||
{
|
||||
_tickSource = tickSource;
|
||||
_memoryManager = memory;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks)
|
||||
{
|
||||
return new NceExecutionContext(exceptionCallbacks);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Execute(IExecutionContext context, ulong address)
|
||||
{
|
||||
NceExecutionContext nec = (NceExecutionContext)context;
|
||||
NceNativeInterface.RegisterThread(nec, _tickSource);
|
||||
int tableIndex = NceThreadTable.Register(_getTpidrEl0(), nec.NativeContextPtr);
|
||||
|
||||
nec.SetStartAddress(address);
|
||||
_threadStart(nec.NativeContextPtr);
|
||||
nec.Exit();
|
||||
|
||||
NceThreadTable.Unregister(tableIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void InvalidateCacheRegion(ulong address, ulong size)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||
{
|
||||
return new DummyDiskCacheLoadState();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PrepareCodeRange(ulong address, ulong size)
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using ARMeilleure.Memory;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce
|
||||
{
|
||||
public class NceEngine : ICpuEngine
|
||||
{
|
||||
public ITickSource TickSource{ get; }
|
||||
|
||||
public NceEngine(ITickSource tickSource)
|
||||
{
|
||||
TickSource = tickSource;
|
||||
}
|
||||
|
||||
public ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit)
|
||||
{
|
||||
return new NceCpuContext(TickSource, memoryManager, for64Bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Cpu.Signal;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce
|
||||
{
|
||||
class NceExecutionContext : IExecutionContext
|
||||
{
|
||||
private const ulong AlternateStackSize = 0x4000;
|
||||
|
||||
private readonly NceNativeContext _context;
|
||||
private readonly ExceptionCallbacks _exceptionCallbacks;
|
||||
|
||||
internal IntPtr NativeContextPtr => _context.BasePtr;
|
||||
|
||||
public ulong Pc => 0UL;
|
||||
|
||||
public long TpidrEl0
|
||||
{
|
||||
get => (long)_context.GetStorage().TpidrEl0;
|
||||
set => _context.GetStorage().TpidrEl0 = (ulong)value;
|
||||
}
|
||||
|
||||
public long TpidrroEl0
|
||||
{
|
||||
get => (long)_context.GetStorage().TpidrroEl0;
|
||||
set => _context.GetStorage().TpidrroEl0 = (ulong)value;
|
||||
}
|
||||
|
||||
public uint Pstate
|
||||
{
|
||||
get => _context.GetStorage().Pstate;
|
||||
set => _context.GetStorage().Pstate = value;
|
||||
}
|
||||
|
||||
public uint Fpcr
|
||||
{
|
||||
get => _context.GetStorage().Fpcr;
|
||||
set => _context.GetStorage().Fpcr = value;
|
||||
}
|
||||
|
||||
public uint Fpsr
|
||||
{
|
||||
get => _context.GetStorage().Fpsr;
|
||||
set => _context.GetStorage().Fpsr = value;
|
||||
}
|
||||
|
||||
public bool IsAarch32
|
||||
{
|
||||
get => false;
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Running { get; private set; }
|
||||
|
||||
private delegate bool SupervisorCallHandler(int imm);
|
||||
private SupervisorCallHandler _svcHandler;
|
||||
|
||||
private MemoryBlock _alternateStackMemory;
|
||||
|
||||
public NceExecutionContext(ExceptionCallbacks exceptionCallbacks)
|
||||
{
|
||||
_svcHandler = OnSupervisorCall;
|
||||
IntPtr svcHandlerPtr = Marshal.GetFunctionPointerForDelegate(_svcHandler);
|
||||
|
||||
_context = new NceNativeContext();
|
||||
|
||||
ref var storage = ref _context.GetStorage();
|
||||
storage.SvcCallHandler = svcHandlerPtr;
|
||||
storage.InManaged = 1u;
|
||||
storage.CtrEl0 = 0x8444c004; // TODO: Get value from host CPU instead of using guest one?
|
||||
|
||||
Running = true;
|
||||
_exceptionCallbacks = exceptionCallbacks;
|
||||
}
|
||||
|
||||
public ulong GetX(int index) => _context.GetStorage().X[index];
|
||||
public void SetX(int index, ulong value) => _context.GetStorage().X[index] = value;
|
||||
|
||||
public V128 GetV(int index) => _context.GetStorage().V[index];
|
||||
public void SetV(int index, V128 value) => _context.GetStorage().V[index] = value;
|
||||
|
||||
// TODO
|
||||
public bool GetPstateFlag(PState flag) => false;
|
||||
public void SetPstateFlag(PState flag, bool value) { }
|
||||
|
||||
// TODO
|
||||
public bool GetFPstateFlag(FPState flag) => false;
|
||||
public void SetFPstateFlag(FPState flag, bool value) { }
|
||||
|
||||
public void SetStartAddress(ulong address)
|
||||
{
|
||||
ref var storage = ref _context.GetStorage();
|
||||
storage.X[30] = address;
|
||||
storage.HostThreadHandle = NceThreadPal.GetCurrentThreadHandle();
|
||||
|
||||
RegisterAlternateStack();
|
||||
}
|
||||
|
||||
public void Exit()
|
||||
{
|
||||
_context.GetStorage().HostThreadHandle = IntPtr.Zero;
|
||||
|
||||
UnregisterAlternateStack();
|
||||
}
|
||||
|
||||
private void RegisterAlternateStack()
|
||||
{
|
||||
// We need to use an alternate stack to handle the suspend signal,
|
||||
// as the guest stack may be in a state that is not suitable for the signal handlers.
|
||||
|
||||
_alternateStackMemory = new MemoryBlock(AlternateStackSize);
|
||||
NativeSignalHandler.InstallUnixAlternateStackForCurrentThread(_alternateStackMemory.GetPointer(0UL, AlternateStackSize), AlternateStackSize);
|
||||
}
|
||||
|
||||
private void UnregisterAlternateStack()
|
||||
{
|
||||
NativeSignalHandler.UninstallUnixAlternateStackForCurrentThread();
|
||||
_alternateStackMemory.Dispose();
|
||||
_alternateStackMemory = null;
|
||||
}
|
||||
|
||||
public bool OnSupervisorCall(int imm)
|
||||
{
|
||||
_exceptionCallbacks.SupervisorCallback?.Invoke(this, 0UL, imm);
|
||||
return Running;
|
||||
}
|
||||
|
||||
public bool OnInterrupt()
|
||||
{
|
||||
_exceptionCallbacks.InterruptCallback?.Invoke(this);
|
||||
return Running;
|
||||
}
|
||||
|
||||
public void RequestInterrupt()
|
||||
{
|
||||
IntPtr threadHandle = _context.GetStorage().HostThreadHandle;
|
||||
if (threadHandle != IntPtr.Zero)
|
||||
{
|
||||
// Bit 0 set means that the thread is currently running managed code.
|
||||
// Bit 1 set means that an interrupt was requested for the thread.
|
||||
// This, we only need to send the suspend signal if the value was 0 (not running managed code,
|
||||
// and no interrupt was requested before).
|
||||
|
||||
ref uint inManaged = ref _context.GetStorage().InManaged;
|
||||
uint oldValue = Interlocked.Or(ref inManaged, 2);
|
||||
|
||||
if (oldValue == 0)
|
||||
{
|
||||
NceThreadPal.SuspendThread(threadHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void StopRunning()
|
||||
{
|
||||
Running = false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_context.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Memory;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce
|
||||
{
|
||||
class NceMemoryAllocator : IJitMemoryAllocator
|
||||
{
|
||||
public IJitMemoryBlock Allocate(ulong size) => new NceMemoryBlock(size, MemoryAllocationFlags.None);
|
||||
public IJitMemoryBlock Reserve(ulong size) => new NceMemoryBlock(size, MemoryAllocationFlags.Reserve);
|
||||
|
||||
public ulong GetPageSize() => MemoryBlock.GetPageSize();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce
|
||||
{
|
||||
class NceMemoryBlock : IJitMemoryBlock
|
||||
{
|
||||
private readonly MemoryBlock _impl;
|
||||
|
||||
public IntPtr Pointer => _impl.Pointer;
|
||||
|
||||
public NceMemoryBlock(ulong size, MemoryAllocationFlags flags)
|
||||
{
|
||||
_impl = new MemoryBlock(size, flags);
|
||||
}
|
||||
|
||||
public void Commit(ulong offset, ulong size) => _impl.Commit(offset, size);
|
||||
public void MapAsRw(ulong offset, ulong size) => _impl.Reprotect(offset, size, MemoryPermission.ReadAndWrite);
|
||||
public void MapAsRx(ulong offset, ulong size) => _impl.Reprotect(offset, size, MemoryPermission.ReadAndExecute);
|
||||
public void MapAsRwx(ulong offset, ulong size) => _impl.Reprotect(offset, size, MemoryPermission.ReadWriteExecute);
|
||||
|
||||
public void Dispose() => _impl.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce
|
||||
{
|
||||
class NceNativeContext : IDisposable
|
||||
{
|
||||
public struct NativeCtxStorage
|
||||
{
|
||||
public Array32<ulong> X;
|
||||
public Array32<V128> V;
|
||||
public ulong TpidrEl0;
|
||||
public ulong TpidrroEl0;
|
||||
public ulong CtrEl0;
|
||||
public uint Pstate;
|
||||
public uint Fpcr;
|
||||
public uint Fpsr;
|
||||
public uint InManaged;
|
||||
public ulong HostSp;
|
||||
public IntPtr HostThreadHandle;
|
||||
public ulong TempStorage;
|
||||
public IntPtr SvcCallHandler;
|
||||
}
|
||||
|
||||
private static NativeCtxStorage _dummyStorage = new();
|
||||
|
||||
private readonly MemoryBlock _block;
|
||||
|
||||
public IntPtr BasePtr => _block.Pointer;
|
||||
|
||||
public NceNativeContext()
|
||||
{
|
||||
_block = new MemoryBlock((ulong)Unsafe.SizeOf<NativeCtxStorage>());
|
||||
}
|
||||
|
||||
public static int GetXOffset(int index)
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.X[index]);
|
||||
}
|
||||
|
||||
public static int GetGuestSPOffset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.X[31]);
|
||||
}
|
||||
|
||||
public static int GetVOffset(int index)
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.V[index]);
|
||||
}
|
||||
|
||||
public static int GetTpidrEl0Offset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrEl0);
|
||||
}
|
||||
|
||||
public static int GetTpidrroEl0Offset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrroEl0);
|
||||
}
|
||||
|
||||
public static int GetInManagedOffset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.InManaged);
|
||||
}
|
||||
|
||||
public static int GetHostSPOffset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.HostSp);
|
||||
}
|
||||
|
||||
public static int GetCtrEl0Offset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.CtrEl0);
|
||||
}
|
||||
|
||||
public static int GetTempStorageOffset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TempStorage);
|
||||
}
|
||||
|
||||
public static int GetSvcCallHandlerOffset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.SvcCallHandler);
|
||||
}
|
||||
|
||||
private static int StorageOffset<T>(ref NativeCtxStorage storage, ref T target)
|
||||
{
|
||||
return (int)Unsafe.ByteOffset(ref Unsafe.As<NativeCtxStorage, T>(ref storage), ref target);
|
||||
}
|
||||
|
||||
public unsafe ref NativeCtxStorage GetStorage() => ref Unsafe.AsRef<NativeCtxStorage>((void*)_block.Pointer);
|
||||
|
||||
public void Dispose() => _block.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce
|
||||
{
|
||||
static class NceNativeInterface
|
||||
{
|
||||
private delegate ulong GetTickCounterDelegate();
|
||||
private delegate bool SuspendThreadHandlerDelegate();
|
||||
private static GetTickCounterDelegate _getTickCounter;
|
||||
private static SuspendThreadHandlerDelegate _suspendThreadHandler;
|
||||
private static IntPtr _getTickCounterPtr;
|
||||
private static IntPtr _suspendThreadHandlerPtr;
|
||||
|
||||
[ThreadStatic]
|
||||
private static NceExecutionContext _context;
|
||||
|
||||
[ThreadStatic]
|
||||
private static ITickSource _tickSource;
|
||||
|
||||
static NceNativeInterface()
|
||||
{
|
||||
_getTickCounter = GetTickCounter;
|
||||
_suspendThreadHandler = SuspendThreadHandler;
|
||||
_getTickCounterPtr = Marshal.GetFunctionPointerForDelegate(_getTickCounter);
|
||||
_suspendThreadHandlerPtr = Marshal.GetFunctionPointerForDelegate(_suspendThreadHandler);
|
||||
}
|
||||
|
||||
public static void RegisterThread(NceExecutionContext context, ITickSource tickSource)
|
||||
{
|
||||
_context = context;
|
||||
_tickSource = tickSource;
|
||||
}
|
||||
|
||||
public static ulong GetTickCounter()
|
||||
{
|
||||
return _tickSource.Counter;
|
||||
}
|
||||
|
||||
public static bool SuspendThreadHandler()
|
||||
{
|
||||
return _context.OnInterrupt();
|
||||
}
|
||||
|
||||
public static IntPtr GetTickCounterAccessFunctionPointer()
|
||||
{
|
||||
return _getTickCounterPtr;
|
||||
}
|
||||
|
||||
public static IntPtr GetSuspendThreadHandlerFunctionPointer()
|
||||
{
|
||||
return _suspendThreadHandlerPtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,603 @@
|
||||
using Ryujinx.Cpu.Nce.Arm64;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce
|
||||
{
|
||||
public static class NcePatcher
|
||||
{
|
||||
private const int ScratchBaseReg = 19;
|
||||
|
||||
private const uint IntCalleeSavedRegsMask = 0x1ff80000; // X19 to X28
|
||||
private const uint FpCalleeSavedRegsMask = 0xff00; // D8 to D15
|
||||
|
||||
public static NceCpuCodePatch CreatePatch(ReadOnlySpan<byte> textSection)
|
||||
{
|
||||
NceCpuCodePatch codePatch = new();
|
||||
|
||||
var textUint = MemoryMarshal.Cast<byte, uint>(textSection);
|
||||
|
||||
for (int i = 0; i < textUint.Length; i++)
|
||||
{
|
||||
uint inst = textUint[i];
|
||||
ulong address = (ulong)i * sizeof(uint);
|
||||
|
||||
if ((inst & ~(0xffffu << 5)) == 0xd4000001u) // svc #0
|
||||
{
|
||||
uint svcId = (ushort)(inst >> 5);
|
||||
codePatch.AddCode(i, WriteSvcPatch(svcId));
|
||||
Logger.Debug?.Print(LogClass.Cpu, $"Patched SVC #{svcId} at 0x{address:X}.");
|
||||
}
|
||||
else if ((inst & ~0x1f) == 0xd53bd060) // mrs x0, tpidrro_el0
|
||||
{
|
||||
uint rd = inst & 0x1f;
|
||||
codePatch.AddCode(i, WriteMrsTpidrroEl0Patch(rd));
|
||||
Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, tpidrro_el0 at 0x{address:X}.");
|
||||
}
|
||||
else if ((inst & ~0x1f) == 0xd53bd040) // mrs x0, tpidr_el0
|
||||
{
|
||||
uint rd = inst & 0x1f;
|
||||
codePatch.AddCode(i, WriteMrsTpidrEl0Patch(rd));
|
||||
Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, tpidr_el0 at 0x{address:X}.");
|
||||
}
|
||||
else if ((inst & ~0x1f) == 0xd53b0020 && OperatingSystem.IsMacOS()) // mrs x0, ctr_el0
|
||||
{
|
||||
uint rd = inst & 0x1f;
|
||||
codePatch.AddCode(i, WriteMrsCtrEl0Patch(rd));
|
||||
Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, ctr_el0 at 0x{address:X}.");
|
||||
}
|
||||
else if ((inst & ~0x1f) == 0xd53be020) // mrs x0, cntpct_el0
|
||||
{
|
||||
uint rd = inst & 0x1f;
|
||||
codePatch.AddCode(i, WriteMrsCntpctEl0Patch(rd));
|
||||
Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, cntpct_el0 at 0x{address:X}.");
|
||||
}
|
||||
else if ((inst & ~0x1f) == 0xd51bd040) // msr tpidr_el0, x0
|
||||
{
|
||||
uint rd = inst & 0x1f;
|
||||
codePatch.AddCode(i, WriteMsrTpidrEl0Patch(rd));
|
||||
Logger.Debug?.Print(LogClass.Cpu, $"Patched MSR tpidr_el0, x{rd} at 0x{address:X}.");
|
||||
}
|
||||
}
|
||||
|
||||
return codePatch;
|
||||
}
|
||||
|
||||
private static uint[] WriteSvcPatch(uint svcId)
|
||||
{
|
||||
Assembler asm = new();
|
||||
|
||||
WriteManagedCall(asm, (asm, ctx, tmp, tmp2) =>
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
asm.StrRiUn(Gpr(i), ctx, NceNativeContext.GetXOffset(i));
|
||||
}
|
||||
|
||||
WriteInManagedLockAcquire(asm, ctx, tmp, tmp2);
|
||||
|
||||
asm.Mov(Gpr(0, OperandType.I32), svcId);
|
||||
asm.LdrRiUn(tmp, ctx, NceNativeContext.GetSvcCallHandlerOffset());
|
||||
asm.Blr(tmp);
|
||||
|
||||
Operand lblContinue = asm.CreateLabel();
|
||||
Operand lblQuit = asm.CreateLabel();
|
||||
|
||||
asm.Cbnz(Gpr(0, OperandType.I32), lblContinue);
|
||||
|
||||
asm.MarkLabel(lblQuit);
|
||||
|
||||
CreateRegisterSaveRestoreForManaged().WriteEpilogue(asm);
|
||||
|
||||
asm.Ret(Gpr(30));
|
||||
|
||||
asm.MarkLabel(lblContinue);
|
||||
|
||||
WriteInManagedLockRelease(asm, ctx, tmp, tmp2, ThreadExitMethod.Label, lblQuit);
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
asm.LdrRiUn(Gpr(i), ctx, NceNativeContext.GetXOffset(i));
|
||||
}
|
||||
}, 0xff);
|
||||
|
||||
asm.B(0);
|
||||
|
||||
return asm.GetCode();
|
||||
}
|
||||
|
||||
private static uint[] WriteMrsTpidrroEl0Patch(uint rd)
|
||||
{
|
||||
return WriteMrsContextRead(rd, NceNativeContext.GetTpidrroEl0Offset());
|
||||
}
|
||||
|
||||
private static uint[] WriteMrsTpidrEl0Patch(uint rd)
|
||||
{
|
||||
return WriteMrsContextRead(rd, NceNativeContext.GetTpidrEl0Offset());
|
||||
}
|
||||
|
||||
private static uint[] WriteMrsCtrEl0Patch(uint rd)
|
||||
{
|
||||
return WriteMrsContextRead(rd, NceNativeContext.GetCtrEl0Offset());
|
||||
}
|
||||
|
||||
private static uint[] WriteMrsCntpctEl0Patch(uint rd)
|
||||
{
|
||||
Assembler asm = new();
|
||||
|
||||
WriteManagedCall(asm, (asm, ctx, tmp, tmp2) =>
|
||||
{
|
||||
WriteInManagedLockAcquire(asm, ctx, tmp, tmp2);
|
||||
|
||||
asm.Mov(tmp, (ulong)NceNativeInterface.GetTickCounterAccessFunctionPointer());
|
||||
asm.Blr(tmp);
|
||||
asm.StrRiUn(Gpr(0), ctx, NceNativeContext.GetTempStorageOffset());
|
||||
|
||||
WriteInManagedLockRelease(asm, ctx, tmp, tmp2, ThreadExitMethod.GenerateReturn);
|
||||
|
||||
asm.LdrRiUn(Gpr((int)rd), ctx, NceNativeContext.GetTempStorageOffset());
|
||||
}, 1u << (int)rd);
|
||||
|
||||
asm.B(0);
|
||||
|
||||
return asm.GetCode();
|
||||
}
|
||||
|
||||
private static uint[] WriteMsrTpidrEl0Patch(uint rd)
|
||||
{
|
||||
Assembler asm = new();
|
||||
|
||||
Span<int> scratchRegs = stackalloc int[3];
|
||||
PickScratchRegs(scratchRegs, 1u << (int)rd);
|
||||
|
||||
RegisterSaveRestore rsr = new((1 << scratchRegs[0]) | (1 << scratchRegs[1]) | (1 << scratchRegs[2]));
|
||||
|
||||
rsr.WritePrologue(asm);
|
||||
|
||||
WriteLoadContext(asm, Gpr(scratchRegs[0]), Gpr(scratchRegs[1]), Gpr(scratchRegs[2]));
|
||||
asm.StrRiUn(Gpr((int)rd), Gpr(scratchRegs[0]),NceNativeContext.GetTpidrEl0Offset());
|
||||
|
||||
rsr.WriteEpilogue(asm);
|
||||
|
||||
asm.B(0);
|
||||
|
||||
return asm.GetCode();
|
||||
}
|
||||
|
||||
private static uint[] WriteMrsContextRead(uint rd, int contextOffset)
|
||||
{
|
||||
Assembler asm = new();
|
||||
|
||||
Span<int> scratchRegs = stackalloc int[3];
|
||||
PickScratchRegs(scratchRegs, 1u << (int)rd);
|
||||
|
||||
RegisterSaveRestore rsr = new((1 << scratchRegs[0]) | (1 << scratchRegs[1]) | (1 << scratchRegs[2]));
|
||||
|
||||
rsr.WritePrologue(asm);
|
||||
|
||||
WriteLoadContext(asm, Gpr(scratchRegs[0]), Gpr(scratchRegs[1]), Gpr(scratchRegs[2]));
|
||||
asm.Add(Gpr((int)rd), Gpr(scratchRegs[0]), Const((ulong)contextOffset));
|
||||
|
||||
rsr.WriteEpilogue(asm);
|
||||
|
||||
asm.LdrRiUn(Gpr((int)rd), Gpr((int)rd), 0);
|
||||
|
||||
asm.B(0);
|
||||
|
||||
return asm.GetCode();
|
||||
}
|
||||
|
||||
private static void WriteLoadContext(Assembler asm, Operand tmp0, Operand tmp1, Operand tmp2)
|
||||
{
|
||||
asm.Mov(tmp0, (ulong)NceThreadTable.EntriesPointer);
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
asm.MrsTpidrroEl0(tmp1);
|
||||
}
|
||||
else
|
||||
{
|
||||
asm.MrsTpidrEl0(tmp1);
|
||||
}
|
||||
|
||||
Operand lblFound = asm.CreateLabel();
|
||||
Operand lblLoop = asm.CreateLabel();
|
||||
|
||||
asm.MarkLabel(lblLoop);
|
||||
|
||||
asm.LdrRiPost(tmp2, tmp0, 16);
|
||||
asm.Cmp(tmp1, tmp2);
|
||||
asm.B(lblFound, ArmCondition.Eq);
|
||||
asm.B(lblLoop);
|
||||
|
||||
asm.MarkLabel(lblFound);
|
||||
|
||||
asm.Ldur(tmp0, tmp0, -8);
|
||||
}
|
||||
|
||||
private static void WriteLoadContextSafe(Assembler asm, Operand lblFail, Operand tmp0, Operand tmp1, Operand tmp2, Operand tmp3)
|
||||
{
|
||||
asm.Mov(tmp0, (ulong)NceThreadTable.EntriesPointer);
|
||||
asm.Ldur(tmp3, tmp0, -8);
|
||||
asm.Add(tmp3, tmp0, tmp3, ArmShiftType.Lsl, 4);
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
asm.MrsTpidrroEl0(tmp1);
|
||||
}
|
||||
else
|
||||
{
|
||||
asm.MrsTpidrEl0(tmp1);
|
||||
}
|
||||
|
||||
Operand lblFound = asm.CreateLabel();
|
||||
Operand lblLoop = asm.CreateLabel();
|
||||
|
||||
asm.MarkLabel(lblLoop);
|
||||
|
||||
asm.Cmp(tmp0, tmp3);
|
||||
asm.B(lblFail, ArmCondition.GeUn);
|
||||
asm.LdrRiPost(tmp2, tmp0, 16);
|
||||
asm.Cmp(tmp1, tmp2);
|
||||
asm.B(lblFound, ArmCondition.Eq);
|
||||
asm.B(lblLoop);
|
||||
|
||||
asm.MarkLabel(lblFound);
|
||||
|
||||
asm.Ldur(tmp0, tmp0, -8);
|
||||
}
|
||||
|
||||
private static void PickScratchRegs(Span<int> scratchRegs, uint blacklistedRegMask)
|
||||
{
|
||||
int scratchReg = ScratchBaseReg;
|
||||
|
||||
for (int i = 0; i < scratchRegs.Length; i++)
|
||||
{
|
||||
while ((blacklistedRegMask & (1u << scratchReg)) != 0)
|
||||
{
|
||||
scratchReg++;
|
||||
}
|
||||
|
||||
if (scratchReg >= 29)
|
||||
{
|
||||
throw new ArgumentException($"No enough register for {scratchRegs.Length} scratch register, started from {ScratchBaseReg}");
|
||||
}
|
||||
|
||||
scratchRegs[i] = scratchReg++;
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand Gpr(int register, OperandType type = OperandType.I64)
|
||||
{
|
||||
return new Operand(register, RegisterType.Integer, type);
|
||||
}
|
||||
|
||||
private static Operand Vec(int register, OperandType type = OperandType.V128)
|
||||
{
|
||||
return new Operand(register, RegisterType.Vector, type);
|
||||
}
|
||||
|
||||
private static Operand Const(ulong value)
|
||||
{
|
||||
return new Operand(OperandType.I64, value);
|
||||
}
|
||||
|
||||
private static Operand Const(OperandType type, ulong value)
|
||||
{
|
||||
return new Operand(type, value);
|
||||
}
|
||||
|
||||
private static uint GetImm26(ulong sourceAddress, ulong targetAddress)
|
||||
{
|
||||
long offset = (long)(targetAddress - sourceAddress);
|
||||
long offsetTrunc = (offset >> 2) & 0x3FFFFFF;
|
||||
|
||||
if ((offsetTrunc << 38) >> 36 != offset)
|
||||
{
|
||||
throw new Exception($"Offset out of range: 0x{sourceAddress:X} -> 0x{targetAddress:X} (0x{offset:X})");
|
||||
}
|
||||
|
||||
return (uint)offsetTrunc;
|
||||
}
|
||||
|
||||
private static int GetOffset(ulong sourceAddress, ulong targetAddress)
|
||||
{
|
||||
long offset = (long)(targetAddress - sourceAddress);
|
||||
|
||||
return checked((int)offset);
|
||||
}
|
||||
|
||||
private static uint[] GetCopy(uint[] code)
|
||||
{
|
||||
uint[] codeCopy = new uint[code.Length];
|
||||
code.CopyTo(codeCopy, 0);
|
||||
|
||||
return codeCopy;
|
||||
}
|
||||
|
||||
private static void WriteManagedCall(Assembler asm, Action<Assembler, Operand, Operand, Operand> writeCall, uint blacklistedRegMask)
|
||||
{
|
||||
int intMask = 0x7fffffff & (int)~blacklistedRegMask;
|
||||
int vecMask = unchecked((int)0xffffffff);
|
||||
|
||||
Span<int> scratchRegs = stackalloc int[3];
|
||||
PickScratchRegs(scratchRegs, blacklistedRegMask);
|
||||
|
||||
RegisterSaveRestore rsr = new(intMask, vecMask, OperandType.V128);
|
||||
|
||||
rsr.WritePrologue(asm);
|
||||
|
||||
WriteLoadContext(asm, Gpr(scratchRegs[0]), Gpr(scratchRegs[1]), Gpr(scratchRegs[2]));
|
||||
|
||||
asm.MovSp(Gpr(scratchRegs[1]), Gpr(Assembler.SpRegister));
|
||||
asm.StrRiUn(Gpr(scratchRegs[1]), Gpr(scratchRegs[0]), NceNativeContext.GetGuestSPOffset());
|
||||
asm.LdrRiUn(Gpr(scratchRegs[1]), Gpr(scratchRegs[0]), NceNativeContext.GetHostSPOffset());
|
||||
asm.MovSp(Gpr(Assembler.SpRegister), Gpr(scratchRegs[1]));
|
||||
|
||||
writeCall(asm, Gpr(scratchRegs[0]), Gpr(scratchRegs[1]), Gpr(scratchRegs[2]));
|
||||
|
||||
asm.LdrRiUn(Gpr(scratchRegs[1]), Gpr(scratchRegs[0]), NceNativeContext.GetGuestSPOffset());
|
||||
asm.MovSp(Gpr(Assembler.SpRegister), Gpr(scratchRegs[1]));
|
||||
|
||||
rsr.WriteEpilogue(asm);
|
||||
}
|
||||
|
||||
internal static uint[] GenerateThreadStartCode()
|
||||
{
|
||||
Assembler asm = new();
|
||||
|
||||
CreateRegisterSaveRestoreForManaged().WritePrologue(asm);
|
||||
|
||||
asm.MovSp(Gpr(1), Gpr(Assembler.SpRegister));
|
||||
asm.StrRiUn(Gpr(1), Gpr(0), NceNativeContext.GetHostSPOffset());
|
||||
|
||||
for (int i = 2; i < 30; i += 2)
|
||||
{
|
||||
asm.LdpRiUn(Gpr(i), Gpr(i + 1), Gpr(0), NceNativeContext.GetXOffset(i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 32; i += 2)
|
||||
{
|
||||
asm.LdpRiUn(Vec(i), Vec(i + 1), Gpr(0), NceNativeContext.GetVOffset(i));
|
||||
}
|
||||
|
||||
asm.LdpRiUn(Gpr(30), Gpr(1), Gpr(0), NceNativeContext.GetXOffset(30));
|
||||
asm.MovSp(Gpr(Assembler.SpRegister), Gpr(1));
|
||||
|
||||
asm.StrRiUn(Gpr(Assembler.ZrRegister, OperandType.I32), Gpr(0), NceNativeContext.GetInManagedOffset());
|
||||
|
||||
asm.LdpRiUn(Gpr(0), Gpr(1), Gpr(0), NceNativeContext.GetXOffset(0));
|
||||
asm.Br(Gpr(30));
|
||||
|
||||
return asm.GetCode();
|
||||
}
|
||||
|
||||
internal static uint[] GenerateSuspendExceptionHandler()
|
||||
{
|
||||
Assembler asm = new();
|
||||
|
||||
Span<int> scratchRegs = stackalloc int[4];
|
||||
PickScratchRegs(scratchRegs, 0u);
|
||||
|
||||
RegisterSaveRestore rsr = new((1 << scratchRegs[0]) | (1 << scratchRegs[1]) | (1 << scratchRegs[2]) | (1 << scratchRegs[3]), hasCall: true);
|
||||
|
||||
rsr.WritePrologue(asm);
|
||||
|
||||
Operand lblAgain = asm.CreateLabel();
|
||||
Operand lblFail = asm.CreateLabel();
|
||||
|
||||
WriteLoadContextSafe(asm, lblFail, Gpr(scratchRegs[0]), Gpr(scratchRegs[1]), Gpr(scratchRegs[2]), Gpr(scratchRegs[3]));
|
||||
|
||||
asm.LdrRiUn(Gpr(scratchRegs[1]), Gpr(scratchRegs[0]), NceNativeContext.GetHostSPOffset());
|
||||
asm.MovSp(Gpr(scratchRegs[2]), Gpr(Assembler.SpRegister));
|
||||
asm.MovSp(Gpr(Assembler.SpRegister), Gpr(scratchRegs[1]));
|
||||
|
||||
asm.Cmp(Gpr(0, OperandType.I32), Const((ulong)NceThreadPal.UnixSuspendSignal));
|
||||
asm.B(lblFail, ArmCondition.Ne);
|
||||
|
||||
// SigUsr2
|
||||
|
||||
asm.Mov(Gpr(scratchRegs[1], OperandType.I32), Const(OperandType.I32, 1));
|
||||
asm.StrRiUn(Gpr(scratchRegs[1], OperandType.I32), Gpr(scratchRegs[0]), NceNativeContext.GetInManagedOffset());
|
||||
|
||||
asm.MarkLabel(lblAgain);
|
||||
|
||||
asm.Mov(Gpr(scratchRegs[3]), (ulong)NceNativeInterface.GetSuspendThreadHandlerFunctionPointer());
|
||||
asm.Blr(Gpr(scratchRegs[3]));
|
||||
|
||||
// TODO: Check return value, exit if we must.
|
||||
|
||||
WriteInManagedLockReleaseForSuspendHandler(asm, Gpr(scratchRegs[0]), Gpr(scratchRegs[1]), Gpr(scratchRegs[3]), lblAgain);
|
||||
|
||||
asm.MovSp(Gpr(Assembler.SpRegister), Gpr(scratchRegs[2]));
|
||||
|
||||
rsr.WriteEpilogue(asm);
|
||||
|
||||
asm.Ret(Gpr(30));
|
||||
|
||||
asm.MarkLabel(lblFail);
|
||||
|
||||
rsr.WriteEpilogue(asm);
|
||||
|
||||
asm.Ret(Gpr(30));
|
||||
|
||||
return asm.GetCode();
|
||||
}
|
||||
|
||||
internal static uint[] GenerateWrapperExceptionHandler(IntPtr oldSignalHandlerSegfaultPtr, IntPtr signalHandlerPtr)
|
||||
{
|
||||
Assembler asm = new();
|
||||
|
||||
Span<int> scratchRegs = stackalloc int[4];
|
||||
PickScratchRegs(scratchRegs, 0u);
|
||||
|
||||
RegisterSaveRestore rsr = new((1 << scratchRegs[0]) | (1 << scratchRegs[1]) | (1 << scratchRegs[2]) | (1 << scratchRegs[3]), hasCall: true);
|
||||
|
||||
rsr.WritePrologue(asm);
|
||||
|
||||
Operand lblFail = asm.CreateLabel();
|
||||
|
||||
WriteLoadContextSafe(asm, lblFail, Gpr(scratchRegs[0]), Gpr(scratchRegs[1]), Gpr(scratchRegs[2]), Gpr(scratchRegs[3]));
|
||||
|
||||
asm.LdrRiUn(Gpr(scratchRegs[1]), Gpr(scratchRegs[0]), NceNativeContext.GetHostSPOffset());
|
||||
asm.MovSp(Gpr(scratchRegs[2]), Gpr(Assembler.SpRegister));
|
||||
asm.MovSp(Gpr(Assembler.SpRegister), Gpr(scratchRegs[1]));
|
||||
|
||||
// SigSegv
|
||||
|
||||
WriteInManagedLockAcquire(asm, Gpr(scratchRegs[0]), Gpr(scratchRegs[1]), Gpr(scratchRegs[3]));
|
||||
|
||||
asm.Mov(Gpr(scratchRegs[3]), (ulong)signalHandlerPtr);
|
||||
asm.Blr(Gpr(scratchRegs[3]));
|
||||
|
||||
WriteInManagedLockRelease(asm, Gpr(scratchRegs[0]), Gpr(scratchRegs[1]), Gpr(scratchRegs[3]), ThreadExitMethod.None);
|
||||
|
||||
asm.MovSp(Gpr(Assembler.SpRegister), Gpr(scratchRegs[2]));
|
||||
|
||||
rsr.WriteEpilogue(asm);
|
||||
|
||||
asm.Ret(Gpr(30));
|
||||
|
||||
asm.MarkLabel(lblFail);
|
||||
|
||||
rsr.WriteEpilogue(asm);
|
||||
|
||||
asm.Mov(Gpr(3), (ulong)oldSignalHandlerSegfaultPtr);
|
||||
asm.Br(Gpr(3));
|
||||
|
||||
return asm.GetCode();
|
||||
}
|
||||
|
||||
private static void WriteInManagedLockAcquire(Assembler asm, Operand ctx, Operand tmp, Operand tmp2)
|
||||
{
|
||||
Operand tmpUint = new Operand(tmp.GetRegister().Index, RegisterType.Integer, OperandType.I32);
|
||||
Operand tmp2Uint = new Operand(tmp2.GetRegister().Index, RegisterType.Integer, OperandType.I32);
|
||||
|
||||
Operand lblLoop = asm.CreateLabel();
|
||||
|
||||
// Bit 0 set means that the thread is currently executing managed code (that case should be impossible here).
|
||||
// Bit 1 being set means there is a signal pending, we should wait for the signal, otherwise it could trigger
|
||||
// while running managed code.
|
||||
|
||||
asm.MarkLabel(lblLoop);
|
||||
|
||||
asm.Add(tmp, ctx, Const((ulong)NceNativeContext.GetInManagedOffset()));
|
||||
asm.Ldaxr(tmp2Uint, tmp);
|
||||
asm.Cbnz(tmp2Uint, lblLoop);
|
||||
asm.Mov(tmp2Uint, Const(OperandType.I32, 1));
|
||||
asm.Stlxr(tmp2Uint, tmp, tmpUint);
|
||||
asm.Cbnz(tmpUint, lblLoop); // Retry if store failed.
|
||||
}
|
||||
|
||||
private enum ThreadExitMethod
|
||||
{
|
||||
None,
|
||||
GenerateReturn,
|
||||
Label
|
||||
}
|
||||
|
||||
private static void WriteInManagedLockRelease(Assembler asm, Operand ctx, Operand tmp, Operand tmp2, ThreadExitMethod exitMethod, Operand lblQuit = default)
|
||||
{
|
||||
Operand tmpUint = new Operand(tmp.GetRegister().Index, RegisterType.Integer, OperandType.I32);
|
||||
Operand tmp2Uint = new Operand(tmp2.GetRegister().Index, RegisterType.Integer, OperandType.I32);
|
||||
|
||||
Operand lblLoop = asm.CreateLabel();
|
||||
Operand lblInterrupt = asm.CreateLabel();
|
||||
Operand lblDone = asm.CreateLabel();
|
||||
|
||||
// Bit 0 set means that the thread is currently executing managed code (it should be always set here, as we just returned from managed code).
|
||||
// Bit 1 being set means a interrupt was requested while it was in managed, we should service it.
|
||||
|
||||
asm.MarkLabel(lblLoop);
|
||||
|
||||
asm.Add(tmp, ctx, Const((ulong)NceNativeContext.GetInManagedOffset()));
|
||||
asm.Ldaxr(tmp2Uint, tmp);
|
||||
asm.Cmp(tmp2Uint, Const(OperandType.I32, 3));
|
||||
asm.B(lblInterrupt, ArmCondition.Eq);
|
||||
asm.Stlxr(Gpr(Assembler.ZrRegister, OperandType.I32), tmp, tmpUint);
|
||||
asm.Cbnz(tmpUint, lblLoop); // Retry if store failed.
|
||||
asm.B(lblDone);
|
||||
|
||||
asm.MarkLabel(lblInterrupt);
|
||||
|
||||
// If we got here, a interrupt was requested while it was in managed code.
|
||||
// Let's service the interrupt and check what we should do next.
|
||||
|
||||
asm.Mov(tmp2Uint, Const(OperandType.I32, 1));
|
||||
asm.Stlxr(tmp2Uint, tmp, tmpUint);
|
||||
asm.Cbnz(tmpUint, lblLoop); // Retry if store failed.
|
||||
asm.Mov(tmp, (ulong)NceNativeInterface.GetSuspendThreadHandlerFunctionPointer());
|
||||
asm.Blr(tmp);
|
||||
|
||||
// The return value from the interrupt handler indicates if we should continue running.
|
||||
// From here, we either try to release the lock again. We might have received another interrupt
|
||||
// request in the meantime, in which case we should service it again.
|
||||
// If we were requested to exit, then we exit if we can.
|
||||
// TODO: We should also exit while on a signal handler. To do that we need to modify the PC value on the
|
||||
// context. It's a bit more tricky to do, so for now we ignore that case with "ThreadExitMethod.None".
|
||||
|
||||
if (exitMethod == ThreadExitMethod.None)
|
||||
{
|
||||
asm.B(lblLoop);
|
||||
}
|
||||
else
|
||||
{
|
||||
asm.Cbnz(Gpr(0, OperandType.I32), lblLoop);
|
||||
|
||||
if (exitMethod == ThreadExitMethod.Label)
|
||||
{
|
||||
asm.B(lblQuit);
|
||||
}
|
||||
else if (exitMethod == ThreadExitMethod.GenerateReturn)
|
||||
{
|
||||
CreateRegisterSaveRestoreForManaged().WriteEpilogue(asm);
|
||||
|
||||
asm.Ret(Gpr(30));
|
||||
}
|
||||
}
|
||||
|
||||
asm.MarkLabel(lblDone);
|
||||
}
|
||||
|
||||
private static void WriteInManagedLockReleaseForSuspendHandler(Assembler asm, Operand ctx, Operand tmp, Operand tmp2, Operand lblAgain)
|
||||
{
|
||||
Operand tmpUint = new Operand(tmp.GetRegister().Index, RegisterType.Integer, OperandType.I32);
|
||||
Operand tmp2Uint = new Operand(tmp2.GetRegister().Index, RegisterType.Integer, OperandType.I32);
|
||||
|
||||
Operand lblLoop = asm.CreateLabel();
|
||||
Operand lblInterrupt = asm.CreateLabel();
|
||||
Operand lblDone = asm.CreateLabel();
|
||||
|
||||
// Bit 0 set means that the thread is currently executing managed code (it should be always set here, as we just returned from managed code).
|
||||
// Bit 1 being set means a interrupt was requested while it was in managed, we should service it.
|
||||
|
||||
asm.MarkLabel(lblLoop);
|
||||
|
||||
asm.Add(tmp, ctx, Const((ulong)NceNativeContext.GetInManagedOffset()));
|
||||
asm.Ldaxr(tmp2Uint, tmp);
|
||||
asm.Cmp(tmp2Uint, Const(OperandType.I32, 3));
|
||||
asm.B(lblInterrupt, ArmCondition.Eq);
|
||||
asm.Stlxr(Gpr(Assembler.ZrRegister, OperandType.I32), tmp, tmpUint);
|
||||
asm.Cbnz(tmpUint, lblLoop); // Retry if store failed.
|
||||
asm.B(lblDone);
|
||||
|
||||
asm.MarkLabel(lblInterrupt);
|
||||
|
||||
// If we got here, a interrupt was requested while it was in managed code.
|
||||
// Let's service the interrupt and check what we should do next.
|
||||
|
||||
asm.Mov(tmp2Uint, Const(OperandType.I32, 1));
|
||||
asm.Stlxr(tmp2Uint, tmp, tmpUint);
|
||||
asm.Cbnz(tmpUint, lblLoop); // Retry if store failed.
|
||||
asm.B(lblAgain);
|
||||
|
||||
asm.MarkLabel(lblDone);
|
||||
}
|
||||
|
||||
private static RegisterSaveRestore CreateRegisterSaveRestoreForManaged()
|
||||
{
|
||||
return new RegisterSaveRestore((int)IntCalleeSavedRegsMask, unchecked((int)FpCalleeSavedRegsMask), OperandType.FP64, hasCall: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce
|
||||
{
|
||||
static class NceThreadPal
|
||||
{
|
||||
private const int SigUsr2Linux = 12;
|
||||
private const int SigUsr2MacOS = 31;
|
||||
|
||||
public static int UnixSuspendSignal => OperatingSystem.IsMacOS() ? SigUsr2MacOS : SigUsr2Linux;
|
||||
|
||||
public static IntPtr GetCurrentThreadHandle()
|
||||
{
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || Ryujinx.Common.PlatformInfo.IsBionic)
|
||||
{
|
||||
return NceThreadPalUnix.GetCurrentThreadHandle();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void SuspendThread(IntPtr handle)
|
||||
{
|
||||
if (Ryujinx.Common.PlatformInfo.IsBionic)
|
||||
{
|
||||
NceThreadPalAndroid.SuspendThread(handle);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
NceThreadPalUnix.SuspendThread(handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce
|
||||
{
|
||||
static class NceThreadPalAndroid
|
||||
{
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern int pthread_kill(IntPtr thread, int sig);
|
||||
|
||||
public static void SuspendThread(IntPtr handle)
|
||||
{
|
||||
int result = pthread_kill(handle, NceThreadPal.UnixSuspendSignal);
|
||||
if (result != 0)
|
||||
{
|
||||
throw new Exception($"Thread kill returned error 0x{result:X}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce
|
||||
{
|
||||
static class NceThreadPalUnix
|
||||
{
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern IntPtr pthread_self();
|
||||
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern int pthread_threadid_np(IntPtr arg0, out ulong tid);
|
||||
|
||||
[DllImport("libpthread", SetLastError = true)]
|
||||
private static extern int pthread_kill(IntPtr thread, int sig);
|
||||
|
||||
public static IntPtr GetCurrentThreadHandle()
|
||||
{
|
||||
return pthread_self();
|
||||
}
|
||||
|
||||
public static ulong GetCurrentThreadId()
|
||||
{
|
||||
pthread_threadid_np(IntPtr.Zero, out ulong tid);
|
||||
return tid;
|
||||
}
|
||||
|
||||
public static void SuspendThread(IntPtr handle)
|
||||
{
|
||||
int result = pthread_kill(handle, NceThreadPal.UnixSuspendSignal);
|
||||
if (result != 0)
|
||||
{
|
||||
throw new Exception($"Thread kill returned error 0x{result:X}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Nce
|
||||
{
|
||||
static class NceThreadTable
|
||||
{
|
||||
private const int MaxThreads = 4096;
|
||||
|
||||
private struct Entry
|
||||
{
|
||||
public IntPtr ThreadId;
|
||||
public IntPtr NativeContextPtr;
|
||||
|
||||
public Entry(IntPtr threadId, IntPtr nativeContextPtr)
|
||||
{
|
||||
ThreadId = threadId;
|
||||
NativeContextPtr = nativeContextPtr;
|
||||
}
|
||||
}
|
||||
|
||||
private static MemoryBlock _block;
|
||||
|
||||
public static IntPtr EntriesPointer => _block.Pointer + 8;
|
||||
|
||||
static NceThreadTable()
|
||||
{
|
||||
_block = new MemoryBlock((ulong)Unsafe.SizeOf<Entry>() * MaxThreads + 8UL);
|
||||
_block.Write(0UL, 0UL);
|
||||
}
|
||||
|
||||
public static int Register(IntPtr threadId, IntPtr nativeContextPtr)
|
||||
{
|
||||
Span<Entry> entries = GetStorage();
|
||||
|
||||
lock (_block)
|
||||
{
|
||||
ref ulong currentThreadCount = ref GetThreadsCount();
|
||||
|
||||
for (int i = 0; i < MaxThreads; i++)
|
||||
{
|
||||
if (entries[i].ThreadId == IntPtr.Zero)
|
||||
{
|
||||
entries[i] = new Entry(threadId, nativeContextPtr);
|
||||
|
||||
if (currentThreadCount < (ulong)i + 1)
|
||||
{
|
||||
currentThreadCount = (ulong)i + 1;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception($"Number of active threads exceeds limit of {MaxThreads}.");
|
||||
}
|
||||
|
||||
public static void Unregister(int tableIndex)
|
||||
{
|
||||
Span<Entry> entries = GetStorage();
|
||||
|
||||
lock (_block)
|
||||
{
|
||||
if (entries[tableIndex].ThreadId != IntPtr.Zero)
|
||||
{
|
||||
entries[tableIndex] = default;
|
||||
|
||||
ulong currentThreadCount = GetThreadsCount();
|
||||
|
||||
for (int i = (int)currentThreadCount - 1; i >= 0; i--)
|
||||
{
|
||||
if (entries[i].ThreadId != IntPtr.Zero)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
currentThreadCount = (ulong)i;
|
||||
}
|
||||
|
||||
GetThreadsCount() = currentThreadCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ref ulong GetThreadsCount()
|
||||
{
|
||||
return ref _block.GetRef<ulong>(0UL);
|
||||
}
|
||||
|
||||
private static unsafe Span<Entry> GetStorage()
|
||||
{
|
||||
return new Span<Entry>((void*)_block.GetPointer(8UL, (ulong)Unsafe.SizeOf<Entry>() * MaxThreads), MaxThreads);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,10 +89,16 @@ namespace Ryujinx.Cpu.Signal
|
||||
|
||||
ref SignalHandlerConfig config = ref GetConfigRef();
|
||||
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || PlatformInfo.IsBionic)
|
||||
{
|
||||
_signalHandlerPtr = MapCode(NativeSignalHandlerGenerator.GenerateUnixSignalHandler(_handlerConfig, rangeStructSize));
|
||||
|
||||
if (PlatformInfo.IsBionic)
|
||||
{
|
||||
config.StructAddressOffset = 16; // si_addr
|
||||
config.StructWriteOffset = 8; // si_code
|
||||
}
|
||||
|
||||
if (customSignalHandlerFactory != null)
|
||||
{
|
||||
_signalHandlerPtr = customSignalHandlerFactory(UnixSignalHandlerRegistration.GetSegfaultExceptionHandler().sa_handler, _signalHandlerPtr);
|
||||
@@ -122,6 +128,21 @@ namespace Ryujinx.Cpu.Signal
|
||||
}
|
||||
}
|
||||
|
||||
public static void InstallUnixAlternateStackForCurrentThread(IntPtr stackPtr, ulong stackSize)
|
||||
{
|
||||
UnixSignalHandlerRegistration.RegisterAlternateStack(stackPtr, stackSize);
|
||||
}
|
||||
|
||||
public static void UninstallUnixAlternateStackForCurrentThread()
|
||||
{
|
||||
UnixSignalHandlerRegistration.UnregisterAlternateStack();
|
||||
}
|
||||
|
||||
public static void InstallUnixSignalHandler(int sigNum, IntPtr action)
|
||||
{
|
||||
UnixSignalHandlerRegistration.RegisterExceptionHandler(sigNum, action);
|
||||
}
|
||||
|
||||
private static IntPtr MapCode(ReadOnlySpan<byte> code)
|
||||
{
|
||||
Debug.Assert(_codeBlock == null);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Cpu.Signal
|
||||
{
|
||||
@@ -20,26 +21,73 @@ namespace Ryujinx.Cpu.Signal
|
||||
public IntPtr sa_restorer;
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("android"), StructLayout(LayoutKind.Sequential, Pack = 8)]
|
||||
public struct SigActionBionic
|
||||
{
|
||||
public int sa_flags;
|
||||
public IntPtr sa_handler;
|
||||
public SigSet sa_mask;
|
||||
public IntPtr sa_restorer;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 8)]
|
||||
public struct Stack
|
||||
{
|
||||
public IntPtr ss_sp;
|
||||
public int ss_flags;
|
||||
public IntPtr ss_size;
|
||||
}
|
||||
|
||||
private const int SIGSEGV = 11;
|
||||
private const int SIGBUS = 10;
|
||||
private const int SA_SIGINFO = 0x00000004;
|
||||
private const int SA_ONSTACK = 0x08000000;
|
||||
private const int SS_DISABLE = 2;
|
||||
private const int SS_AUTODISARM = 1 << 31;
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int sigaction(int signum, ref SigAction sigAction, out SigAction oldAction);
|
||||
|
||||
[SupportedOSPlatform("android"), LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int sigaction(int signum, ref SigActionBionic sigAction, out SigActionBionic oldAction);
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int sigaction(int signum, IntPtr sigAction, out SigAction oldAction);
|
||||
|
||||
[SupportedOSPlatform("android"), LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int sigaction(int signum, IntPtr sigAction, out SigActionBionic oldAction);
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int sigemptyset(ref SigSet set);
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int sigaltstack(ref Stack ss, out Stack oldSs);
|
||||
|
||||
public static SigAction GetSegfaultExceptionHandler()
|
||||
{
|
||||
int result = sigaction(SIGSEGV, IntPtr.Zero, out SigAction old);
|
||||
int result;
|
||||
SigAction old;
|
||||
|
||||
if (Ryujinx.Common.PlatformInfo.IsBionic)
|
||||
{
|
||||
result = sigaction(SIGSEGV, IntPtr.Zero, out SigActionBionic tmp);
|
||||
|
||||
old = new SigAction
|
||||
{
|
||||
sa_handler = tmp.sa_handler,
|
||||
sa_mask = tmp.sa_mask,
|
||||
sa_flags = tmp.sa_flags,
|
||||
sa_restorer = tmp.sa_restorer
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
result = sigaction(SIGSEGV, IntPtr.Zero, out old);
|
||||
}
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not get SIGSEGV sigaction. Error: {result}");
|
||||
throw new SystemException($"Could not get SIGSEGV sigaction. Error: {Marshal.GetLastPInvokeErrorMessage()}");
|
||||
}
|
||||
|
||||
return old;
|
||||
@@ -47,37 +95,167 @@ namespace Ryujinx.Cpu.Signal
|
||||
|
||||
public static SigAction RegisterExceptionHandler(IntPtr action)
|
||||
{
|
||||
SigAction sig = new()
|
||||
int result;
|
||||
SigAction old;
|
||||
|
||||
if (Ryujinx.Common.PlatformInfo.IsBionic)
|
||||
{
|
||||
sa_handler = action,
|
||||
sa_flags = SA_SIGINFO,
|
||||
};
|
||||
SigActionBionic sig = new()
|
||||
{
|
||||
sa_handler = action,
|
||||
sa_flags = SA_SIGINFO | SA_ONSTACK,
|
||||
};
|
||||
|
||||
sigemptyset(ref sig.sa_mask);
|
||||
sigemptyset(ref sig.sa_mask);
|
||||
|
||||
int result = sigaction(SIGSEGV, ref sig, out SigAction old);
|
||||
result = sigaction(SIGSEGV, ref sig, out SigActionBionic tmp);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not register SIGSEGV sigaction. Error: {result}");
|
||||
old = new SigAction
|
||||
{
|
||||
sa_handler = tmp.sa_handler,
|
||||
sa_mask = tmp.sa_mask,
|
||||
sa_flags = tmp.sa_flags,
|
||||
sa_restorer = tmp.sa_restorer
|
||||
};
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
else
|
||||
{
|
||||
result = sigaction(SIGBUS, ref sig, out _);
|
||||
SigAction sig = new SigAction
|
||||
{
|
||||
sa_handler = action,
|
||||
sa_flags = SA_SIGINFO | SA_ONSTACK,
|
||||
};
|
||||
|
||||
sigemptyset(ref sig.sa_mask);
|
||||
|
||||
result = sigaction(SIGSEGV, ref sig, out old);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not register SIGBUS sigaction. Error: {result}");
|
||||
throw new SystemException($"Could not register SIGSEGV sigaction. Error: {Marshal.GetLastPInvokeErrorMessage()}");
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
|
||||
{
|
||||
result = sigaction(SIGBUS, ref sig, out _);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
throw new SystemException($"Could not register SIGBUS sigaction. Error: {Marshal.GetLastPInvokeErrorMessage()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return old;
|
||||
}
|
||||
|
||||
public static void RegisterAlternateStack(IntPtr stackPtr, ulong stackSize)
|
||||
{
|
||||
Stack stack = new()
|
||||
{
|
||||
ss_sp = stackPtr,
|
||||
ss_flags = SS_AUTODISARM,
|
||||
ss_size = (IntPtr)stackSize
|
||||
};
|
||||
|
||||
int result = sigaltstack(ref stack, out _);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
throw new SystemException($"Could not set alternate stack. Error: {Marshal.GetLastPInvokeErrorMessage()}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnregisterAlternateStack()
|
||||
{
|
||||
Stack stack = new()
|
||||
{
|
||||
ss_flags = SS_DISABLE
|
||||
};
|
||||
|
||||
int result = sigaltstack(ref stack, out _);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
throw new SystemException($"Could not remove alternate stack. Error: {Marshal.GetLastPInvokeErrorMessage()}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void RegisterExceptionHandler(int sigNum, IntPtr action)
|
||||
{
|
||||
int result;
|
||||
|
||||
if (Ryujinx.Common.PlatformInfo.IsBionic)
|
||||
{
|
||||
SigActionBionic sig = new()
|
||||
{
|
||||
sa_handler = action,
|
||||
sa_flags = SA_SIGINFO | SA_ONSTACK
|
||||
};
|
||||
|
||||
sigemptyset(ref sig.sa_mask);
|
||||
|
||||
result = sigaction(sigNum, ref sig, out SigActionBionic oldu);
|
||||
|
||||
if (oldu.sa_handler != IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException($"SIG{sigNum} is already in use.");
|
||||
}
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
throw new SystemException($"Could not register SIG{sigNum} sigaction. Error: {Marshal.GetLastPInvokeErrorMessage()}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SigAction sig = new()
|
||||
{
|
||||
sa_handler = action,
|
||||
sa_flags = SA_SIGINFO | SA_ONSTACK,
|
||||
};
|
||||
|
||||
sigemptyset(ref sig.sa_mask);
|
||||
|
||||
result = sigaction(sigNum, ref sig, out SigAction oldu);
|
||||
|
||||
if (oldu.sa_handler != IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException($"SIG{sigNum} is already in use.");
|
||||
}
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
throw new SystemException($"Could not register SIG{sigNum} sigaction. Error: {Marshal.GetLastPInvokeErrorMessage()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool RestoreExceptionHandler(SigAction oldAction)
|
||||
{
|
||||
return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0 && (!OperatingSystem.IsMacOS() || sigaction(SIGBUS, ref oldAction, out SigAction _) == 0);
|
||||
if (Ryujinx.Common.PlatformInfo.IsBionic)
|
||||
{
|
||||
SigActionBionic tmp = new SigActionBionic
|
||||
{
|
||||
sa_handler = oldAction.sa_handler,
|
||||
sa_mask = oldAction.sa_mask,
|
||||
sa_flags = oldAction.sa_flags,
|
||||
sa_restorer = oldAction.sa_restorer
|
||||
};
|
||||
|
||||
return sigaction(SIGSEGV, ref tmp, out SigActionBionic _) == 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool success = sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0;
|
||||
|
||||
if (success && (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()))
|
||||
{
|
||||
success = sigaction(SIGBUS, ref oldAction, out SigAction _) == 0;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user