Upgrade Ryujinx to greems ryujinx fork

This commit is contained in:
Ben Lawrence 2024-11-29 17:30:56 +13:00
parent c419381e63
commit 538221ef79
2312 changed files with 135449 additions and 39257 deletions

View file

@ -237,7 +237,7 @@ namespace ARMeilleure.CodeGen.Arm64
long originalPosition = _stream.Position;
_stream.Seek(0, SeekOrigin.Begin);
_stream.Read(code, 0, code.Length);
_stream.ReadExactly(code, 0, code.Length);
_stream.Seek(originalPosition, SeekOrigin.Begin);
RelocInfo relocInfo;

View file

@ -20,7 +20,7 @@ namespace ARMeilleure.CodeGen.Arm64
LinuxFeatureInfoHwCap2 = (LinuxFeatureFlagsHwCap2)getauxval(AT_HWCAP2);
}
if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
if (OperatingSystem.IsMacOS())
{
for (int i = 0; i < _sysctlNames.Length; i++)
{
@ -127,14 +127,13 @@ namespace ARMeilleure.CodeGen.Arm64
#region macOS
[LibraryImport("libSystem.dylib", SetLastError = true)]
private static unsafe partial int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string name, out int oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize);
private static unsafe partial int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string name, out int oldValue, ref ulong oldSize, nint newValue, ulong newValueSize);
[SupportedOSPlatform("macos")]
[SupportedOSPlatform("ios")]
private static bool CheckSysctlName(string name)
{
ulong size = sizeof(int);
if (sysctlbyname(name, out int val, ref size, IntPtr.Zero, 0) == 0 && size == sizeof(int))
if (sysctlbyname(name, out int val, ref size, nint.Zero, 0) == 0 && size == sizeof(int))
{
return val != 0;
}

View file

@ -58,9 +58,9 @@ namespace ARMeilleure.CodeGen
/// <typeparam name="T">Type of delegate</typeparam>
/// <param name="codePointer">Pointer to the function code in memory</param>
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
public T MapWithPointer<T>(out IntPtr codePointer, bool deferProtect = false)
public T MapWithPointer<T>(out nint codePointer)
{
codePointer = JitCache.Map(this, deferProtect);
codePointer = JitCache.Map(this);
return Marshal.GetDelegateForFunctionPointer<T>(codePointer);
}

View file

@ -251,7 +251,20 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
}
}
int selectedReg = GetHighestValueIndex(freePositions);
// If this is a copy destination variable, we prefer the register used for the copy source.
// If the register is available, then the copy can be eliminated later as both source
// and destination will use the same register.
int selectedReg;
if (current.TryGetCopySourceRegister(out int preferredReg) && freePositions[preferredReg] >= current.GetEnd())
{
selectedReg = preferredReg;
}
else
{
selectedReg = GetHighestValueIndex(freePositions);
}
int selectedNextUse = freePositions[selectedReg];
// Intervals starts and ends at odd positions, unless they span an entire
@ -431,7 +444,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
}
}
private static int GetHighestValueIndex(Span<int> span)
private static int GetHighestValueIndex(ReadOnlySpan<int> span)
{
int highest = int.MinValue;
@ -798,12 +811,12 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
// The "visited" state is stored in the MSB of the local's value.
const ulong VisitedMask = 1ul << 63;
bool IsVisited(Operand local)
static bool IsVisited(Operand local)
{
return (local.GetValueUnsafe() & VisitedMask) != 0;
}
void SetVisited(Operand local)
static void SetVisited(Operand local)
{
local.GetValueUnsafe() |= VisitedMask;
}
@ -826,9 +839,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{
dest.NumberLocal(_intervals.Count);
_intervals.Add(new LiveInterval(dest));
LiveInterval interval = new LiveInterval(dest);
_intervals.Add(interval);
SetVisited(dest);
// If this is a copy (or copy-like operation), set the copy source interval as well.
// This is used for register preferencing later on, which allows the copy to be eliminated
// in some cases.
if (node.Instruction == Instruction.Copy || node.Instruction == Instruction.ZeroExtend32)
{
Operand source = node.GetSource(0);
if (source.Kind == OperandKind.LocalVariable &&
source.GetLocalNumber() > 0 &&
(node.Instruction == Instruction.Copy || source.Type == OperandType.I32))
{
interval.SetCopySource(_intervals[source.GetLocalNumber()]);
}
}
}
}
}

View file

@ -19,6 +19,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
public LiveRange CurrRange;
public LiveInterval Parent;
public LiveInterval CopySource;
public UseList Uses;
public LiveIntervalList Children;
@ -37,6 +38,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
private ref LiveRange CurrRange => ref _data->CurrRange;
private ref LiveRange PrevRange => ref _data->PrevRange;
private ref LiveInterval Parent => ref _data->Parent;
private ref LiveInterval CopySource => ref _data->CopySource;
private ref UseList Uses => ref _data->Uses;
private ref LiveIntervalList Children => ref _data->Children;
@ -78,6 +80,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
Register = register;
}
public void SetCopySource(LiveInterval copySource)
{
CopySource = copySource;
}
public bool TryGetCopySourceRegister(out int copySourceRegIndex)
{
if (CopySource._data != null)
{
copySourceRegIndex = CopySource.Register.Index;
return true;
}
copySourceRegIndex = 0;
return false;
}
public void Reset()
{
PrevRange = default;
@ -366,7 +387,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
public override int GetHashCode()
{
return HashCode.Combine((IntPtr)_data);
return HashCode.Combine((nint)_data);
}
public override string ToString()

View file

@ -63,7 +63,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
public override int GetHashCode()
{
return HashCode.Combine((IntPtr)_data);
return HashCode.Combine((nint)_data);
}
public override string ToString()

View file

@ -1444,7 +1444,7 @@ namespace ARMeilleure.CodeGen.X86
Span<byte> buffer = new byte[jump.JumpPosition - _stream.Position];
_stream.Read(buffer);
_stream.ReadExactly(buffer);
_stream.Seek(ReservedBytesForJump, SeekOrigin.Current);
codeStream.Write(buffer);

View file

@ -1,252 +0,0 @@
using ARMeilleure.Diagnostics;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace ARMeilleure.Common
{
/// <summary>
/// Represents a table of guest address to a value.
/// </summary>
/// <typeparam name="TEntry">Type of the value</typeparam>
public unsafe class AddressTable<TEntry> : IDisposable where TEntry : unmanaged
{
/// <summary>
/// Represents a level in an <see cref="AddressTable{TEntry}"/>.
/// </summary>
public readonly struct Level
{
/// <summary>
/// Gets the index of the <see cref="Level"/> in the guest address.
/// </summary>
public int Index { get; }
/// <summary>
/// Gets the length of the <see cref="Level"/> in the guest address.
/// </summary>
public int Length { get; }
/// <summary>
/// Gets the mask which masks the bits used by the <see cref="Level"/>.
/// </summary>
public ulong Mask => ((1ul << Length) - 1) << Index;
/// <summary>
/// Initializes a new instance of the <see cref="Level"/> structure with the specified
/// <paramref name="index"/> and <paramref name="length"/>.
/// </summary>
/// <param name="index">Index of the <see cref="Level"/></param>
/// <param name="length">Length of the <see cref="Level"/></param>
public Level(int index, int length)
{
(Index, Length) = (index, length);
}
/// <summary>
/// Gets the value of the <see cref="Level"/> from the specified guest <paramref name="address"/>.
/// </summary>
/// <param name="address">Guest address</param>
/// <returns>Value of the <see cref="Level"/> from the specified guest <paramref name="address"/></returns>
public int GetValue(ulong address)
{
return (int)((address & Mask) >> Index);
}
}
private bool _disposed;
private TEntry** _table;
private readonly List<IntPtr> _pages;
/// <summary>
/// Gets the bits used by the <see cref="Levels"/> of the <see cref="AddressTable{TEntry}"/> instance.
/// </summary>
public ulong Mask { get; }
/// <summary>
/// Gets the <see cref="Level"/>s used by the <see cref="AddressTable{TEntry}"/> instance.
/// </summary>
public Level[] Levels { get; }
/// <summary>
/// Gets or sets the default fill value of newly created leaf pages.
/// </summary>
public TEntry Fill { get; set; }
/// <summary>
/// Gets the base address of the <see cref="EntryTable{TEntry}"/>.
/// </summary>
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
public IntPtr Base
{
get
{
ObjectDisposedException.ThrowIf(_disposed, this);
lock (_pages)
{
return (IntPtr)GetRootPage();
}
}
}
/// <summary>
/// Constructs a new instance of the <see cref="AddressTable{TEntry}"/> class with the specified list of
/// <see cref="Level"/>.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="levels"/> is null</exception>
/// <exception cref="ArgumentException">Length of <paramref name="levels"/> is less than 2</exception>
public AddressTable(Level[] levels)
{
ArgumentNullException.ThrowIfNull(levels);
if (levels.Length < 2)
{
throw new ArgumentException("Table must be at least 2 levels deep.", nameof(levels));
}
_pages = new List<IntPtr>(capacity: 16);
Levels = levels;
Mask = 0;
foreach (var level in Levels)
{
Mask |= level.Mask;
}
}
/// <summary>
/// Determines if the specified <paramref name="address"/> is in the range of the
/// <see cref="AddressTable{TEntry}"/>.
/// </summary>
/// <param name="address">Guest address</param>
/// <returns><see langword="true"/> if is valid; otherwise <see langword="false"/></returns>
public bool IsValid(ulong address)
{
return (address & ~Mask) == 0;
}
/// <summary>
/// Gets a reference to the value at the specified guest <paramref name="address"/>.
/// </summary>
/// <param name="address">Guest address</param>
/// <returns>Reference to the value at the specified guest <paramref name="address"/></returns>
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
/// <exception cref="ArgumentException"><paramref name="address"/> is not mapped</exception>
public ref TEntry GetValue(ulong address)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (!IsValid(address))
{
throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address));
}
lock (_pages)
{
return ref GetPage(address)[Levels[^1].GetValue(address)];
}
}
/// <summary>
/// Gets the leaf page for the specified guest <paramref name="address"/>.
/// </summary>
/// <param name="address">Guest address</param>
/// <returns>Leaf page for the specified guest <paramref name="address"/></returns>
private TEntry* GetPage(ulong address)
{
TEntry** page = GetRootPage();
for (int i = 0; i < Levels.Length - 1; i++)
{
ref Level level = ref Levels[i];
ref TEntry* nextPage = ref page[level.GetValue(address)];
if (nextPage == null)
{
ref Level nextLevel = ref Levels[i + 1];
nextPage = i == Levels.Length - 2 ?
(TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true) :
(TEntry*)Allocate(1 << nextLevel.Length, IntPtr.Zero, leaf: false);
}
page = (TEntry**)nextPage;
}
return (TEntry*)page;
}
/// <summary>
/// Lazily initialize and get the root page of the <see cref="AddressTable{TEntry}"/>.
/// </summary>
/// <returns>Root page of the <see cref="AddressTable{TEntry}"/></returns>
private TEntry** GetRootPage()
{
if (_table == null)
{
_table = (TEntry**)Allocate(1 << Levels[0].Length, fill: IntPtr.Zero, leaf: false);
}
return _table;
}
/// <summary>
/// Allocates a block of memory of the specified type and length.
/// </summary>
/// <typeparam name="T">Type of elements</typeparam>
/// <param name="length">Number of elements</param>
/// <param name="fill">Fill value</param>
/// <param name="leaf"><see langword="true"/> if leaf; otherwise <see langword="false"/></param>
/// <returns>Allocated block</returns>
private IntPtr Allocate<T>(int length, T fill, bool leaf) where T : unmanaged
{
var size = sizeof(T) * length;
var page = (IntPtr)NativeAllocator.Instance.Allocate((uint)size);
var span = new Span<T>((void*)page, length);
span.Fill(fill);
_pages.Add(page);
TranslatorEventSource.Log.AddressTableAllocated(size, leaf);
return page;
}
/// <summary>
/// Releases all resources used by the <see cref="AddressTable{TEntry}"/> instance.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases all unmanaged and optionally managed resources used by the <see cref="AddressTable{TEntry}"/>
/// instance.
/// </summary>
/// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resouces</param>
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
foreach (var page in _pages)
{
Marshal.FreeHGlobal(page);
}
_disposed = true;
}
}
/// <summary>
/// Frees resources used by the <see cref="AddressTable{TEntry}"/> instance.
/// </summary>
~AddressTable()
{
Dispose(false);
}
}
}

View file

@ -0,0 +1,44 @@
namespace ARMeilleure.Common
{
/// <summary>
/// Represents a level in an <see cref="IAddressTable{TEntry}"/>.
/// </summary>
public readonly struct AddressTableLevel
{
/// <summary>
/// Gets the index of the <see cref="Level"/> in the guest address.
/// </summary>
public int Index { get; }
/// <summary>
/// Gets the length of the <see cref="AddressTableLevel"/> in the guest address.
/// </summary>
public int Length { get; }
/// <summary>
/// Gets the mask which masks the bits used by the <see cref="AddressTableLevel"/>.
/// </summary>
public ulong Mask => ((1ul << Length) - 1) << Index;
/// <summary>
/// Initializes a new instance of the <see cref="AddressTableLevel"/> structure with the specified
/// <paramref name="index"/> and <paramref name="length"/>.
/// </summary>
/// <param name="index">Index of the <see cref="AddressTableLevel"/></param>
/// <param name="length">Length of the <see cref="AddressTableLevel"/></param>
public AddressTableLevel(int index, int length)
{
(Index, Length) = (index, length);
}
/// <summary>
/// Gets the value of the <see cref="AddressTableLevel"/> from the specified guest <paramref name="address"/>.
/// </summary>
/// <param name="address">Guest address</param>
/// <returns>Value of the <see cref="AddressTableLevel"/> from the specified guest <paramref name="address"/></returns>
public int GetValue(ulong address)
{
return (int)((address & Mask) >> Index);
}
}
}

View file

@ -0,0 +1,75 @@
namespace ARMeilleure.Common
{
public static class AddressTablePresets
{
private static readonly AddressTableLevel[] _levels64Bit =
new AddressTableLevel[]
{
new(31, 17),
new(23, 8),
new(15, 8),
new( 7, 8),
new( 2, 5),
};
private static readonly AddressTableLevel[] _levels32Bit =
new AddressTableLevel[]
{
new(31, 17),
new(23, 8),
new(15, 8),
new( 7, 8),
new( 1, 6),
};
private static readonly AddressTableLevel[] _levels64BitSparseTiny =
new AddressTableLevel[]
{
new( 11, 28),
new( 2, 9),
};
private static readonly AddressTableLevel[] _levels32BitSparseTiny =
new AddressTableLevel[]
{
new( 10, 22),
new( 1, 9),
};
private static readonly AddressTableLevel[] _levels64BitSparseGiant =
new AddressTableLevel[]
{
new( 38, 1),
new( 2, 36),
};
private static readonly AddressTableLevel[] _levels32BitSparseGiant =
new AddressTableLevel[]
{
new( 31, 1),
new( 1, 30),
};
//high power will run worse on DDR3 systems and some DDR4 systems due to the higher ram utilization
//low power will never run worse than non-sparse, but for most systems it won't be necessary
//high power is always used, but I've left low power in here for future reference
public static AddressTableLevel[] GetArmPreset(bool for64Bits, bool sparse, bool lowPower = false)
{
if (sparse)
{
if (lowPower)
{
return for64Bits ? _levels64BitSparseTiny : _levels32BitSparseTiny;
}
else
{
return for64Bits ? _levels64BitSparseGiant : _levels32BitSparseGiant;
}
}
else
{
return for64Bits ? _levels64Bit : _levels32Bit;
}
}
}
}

View file

@ -2,7 +2,7 @@ using System;
namespace ARMeilleure.Common
{
unsafe abstract class Allocator : IDisposable
public unsafe abstract class Allocator : IDisposable
{
public T* Allocate<T>(ulong count = 1) where T : unmanaged
{

View file

@ -20,7 +20,7 @@ namespace ARMeilleure.Common
private List<PageInfo> _pages;
private readonly ulong _pageSize;
private readonly uint _pageCount;
private readonly List<IntPtr> _extras;
private readonly List<nint> _extras;
public ArenaAllocator(uint pageSize, uint pageCount)
{
@ -31,11 +31,11 @@ namespace ARMeilleure.Common
_pageIndex = -1;
_page = null;
_pages = new List<PageInfo>();
_pages = [];
_pageSize = pageSize;
_pageCount = pageCount;
_extras = new List<IntPtr>();
_extras = [];
}
public Span<T> AllocateSpan<T>(ulong count) where T : unmanaged
@ -64,7 +64,7 @@ namespace ARMeilleure.Common
{
void* extra = NativeAllocator.Instance.Allocate(size);
_extras.Add((IntPtr)extra);
_extras.Add((nint)extra);
return extra;
}
@ -84,7 +84,7 @@ namespace ARMeilleure.Common
{
_page = new PageInfo
{
Pointer = (byte*)NativeAllocator.Instance.Allocate(_pageSize),
Pointer = (byte*)NativeAllocator.Instance.Allocate(_pageSize)
};
_pages.Add(_page);
@ -114,7 +114,7 @@ namespace ARMeilleure.Common
}
// Free extra blocks that are not page-sized
foreach (IntPtr ptr in _extras)
foreach (nint ptr in _extras)
{
NativeAllocator.Instance.Free((void*)ptr);
}
@ -173,7 +173,7 @@ namespace ARMeilleure.Common
NativeAllocator.Instance.Free(info.Pointer);
}
foreach (IntPtr ptr in _extras)
foreach (nint ptr in _extras)
{
NativeAllocator.Instance.Free((void*)ptr);
}

View file

@ -15,7 +15,7 @@ namespace ARMeilleure.Common
private int _freeHint;
private readonly int _pageCapacity; // Number of entries per page.
private readonly int _pageLogCapacity;
private readonly Dictionary<int, IntPtr> _pages;
private readonly Dictionary<int, nint> _pages;
private readonly BitMap _allocated;
/// <summary>
@ -41,7 +41,7 @@ namespace ARMeilleure.Common
}
_allocated = new BitMap(NativeAllocator.Instance);
_pages = new Dictionary<int, IntPtr>();
_pages = new Dictionary<int, nint>();
_pageLogCapacity = BitOperations.Log2((uint)(pageSize / sizeof(TEntry)));
_pageCapacity = 1 << _pageLogCapacity;
}
@ -138,9 +138,9 @@ namespace ARMeilleure.Common
{
var pageIndex = (int)((uint)(index & ~(_pageCapacity - 1)) >> _pageLogCapacity);
if (!_pages.TryGetValue(pageIndex, out IntPtr page))
if (!_pages.TryGetValue(pageIndex, out nint page))
{
page = (IntPtr)NativeAllocator.Instance.Allocate((uint)sizeof(TEntry) * (uint)_pageCapacity);
page = (nint)NativeAllocator.Instance.Allocate((uint)sizeof(TEntry) * (uint)_pageCapacity);
_pages.Add(pageIndex, page);
}

View file

@ -0,0 +1,51 @@
using System;
namespace ARMeilleure.Common
{
public interface IAddressTable<TEntry> : IDisposable where TEntry : unmanaged
{
/// <summary>
/// True if the address table's bottom level is sparsely mapped.
/// This also ensures the second bottom level is filled with a dummy page rather than 0.
/// </summary>
bool Sparse { get; }
/// <summary>
/// Gets the bits used by the <see cref="Levels"/> of the <see cref="IAddressTable{TEntry}"/> instance.
/// </summary>
ulong Mask { get; }
/// <summary>
/// Gets the <see cref="AddressTableLevel"/>s used by the <see cref="IAddressTable{TEntry}"/> instance.
/// </summary>
AddressTableLevel[] Levels { get; }
/// <summary>
/// Gets or sets the default fill value of newly created leaf pages.
/// </summary>
TEntry Fill { get; set; }
/// <summary>
/// Gets the base address of the <see cref="EntryTable{TEntry}"/>.
/// </summary>
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
nint Base { get; }
/// <summary>
/// Determines if the specified <paramref name="address"/> is in the range of the
/// <see cref="IAddressTable{TEntry}"/>.
/// </summary>
/// <param name="address">Guest address</param>
/// <returns><see langword="true"/> if is valid; otherwise <see langword="false"/></returns>
bool IsValid(ulong address);
/// <summary>
/// Gets a reference to the value at the specified guest <paramref name="address"/>.
/// </summary>
/// <param name="address">Guest address</param>
/// <returns>Reference to the value at the specified guest <paramref name="address"/></returns>
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
/// <exception cref="ArgumentException"><paramref name="address"/> is not mapped</exception>
ref TEntry GetValue(ulong address);
}
}

View file

@ -3,13 +3,13 @@ using System.Runtime.InteropServices;
namespace ARMeilleure.Common
{
unsafe sealed class NativeAllocator : Allocator
public unsafe sealed class NativeAllocator : Allocator
{
public static NativeAllocator Instance { get; } = new();
public override void* Allocate(ulong size)
{
void* result = (void*)Marshal.AllocHGlobal((IntPtr)size);
void* result = (void*)Marshal.AllocHGlobal((nint)size);
if (result == null)
{
@ -21,7 +21,7 @@ namespace ARMeilleure.Common
public override void Free(void* block)
{
Marshal.FreeHGlobal((IntPtr)block);
Marshal.FreeHGlobal((nint)block);
}
}
}

View file

@ -517,7 +517,10 @@ namespace ARMeilleure.Decoders
SetA64("0x00111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_V, InstEmit.Sqrshrn_V, OpCodeSimdShImm.Create);
SetA64("0111111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_S, InstEmit.Sqrshrun_S, OpCodeSimdShImm.Create);
SetA64("0x10111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_V, InstEmit.Sqrshrun_V, OpCodeSimdShImm.Create);
SetA64("010111110>>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Si, InstEmit.Sqshl_Si, OpCodeSimdShImm.Create);
SetA64("0>001110<<1xxxxx010011xxxxxxxxxx", InstName.Sqshl_V, InstEmit.Sqshl_V, OpCodeSimdReg.Create);
SetA64("0000111100>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Vi, InstEmit.Sqshl_Vi, OpCodeSimdShImm.Create);
SetA64("010011110>>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Vi, InstEmit.Sqshl_Vi, OpCodeSimdShImm.Create);
SetA64("0101111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_S, InstEmit.Sqshrn_S, OpCodeSimdShImm.Create);
SetA64("0x00111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_V, InstEmit.Sqshrn_V, OpCodeSimdShImm.Create);
SetA64("0111111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_S, InstEmit.Sqshrun_S, OpCodeSimdShImm.Create);
@ -743,6 +746,7 @@ namespace ARMeilleure.Decoders
SetA32("<<<<01101000xxxxxxxxxxxxxx01xxxx", InstName.Pkh, InstEmit32.Pkh, OpCode32AluRsImm.Create);
SetA32("11110101xx01xxxx1111xxxxxxxxxxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create);
SetA32("11110111xx01xxxx1111xxxxxxx0xxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create);
SetA32("<<<<01100010xxxxxxxx11110001xxxx", InstName.Qadd16, InstEmit32.Qadd16, OpCode32AluReg.Create);
SetA32("<<<<011011111111xxxx11110011xxxx", InstName.Rbit, InstEmit32.Rbit, OpCode32AluReg.Create);
SetA32("<<<<011010111111xxxx11110011xxxx", InstName.Rev, InstEmit32.Rev, OpCode32AluReg.Create);
SetA32("<<<<011010111111xxxx11111011xxxx", InstName.Rev16, InstEmit32.Rev16, OpCode32AluReg.Create);
@ -819,6 +823,10 @@ namespace ARMeilleure.Decoders
SetA32("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create);
SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create);
SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create);
SetA32("<<<<01100110xxxxxxxx11110001xxxx", InstName.Uqadd16, InstEmit32.Uqadd16, OpCode32AluReg.Create);
SetA32("<<<<01100110xxxxxxxx11111001xxxx", InstName.Uqadd8, InstEmit32.Uqadd8, OpCode32AluReg.Create);
SetA32("<<<<01100110xxxxxxxx11110111xxxx", InstName.Uqsub16, InstEmit32.Uqsub16, OpCode32AluReg.Create);
SetA32("<<<<01100110xxxxxxxx11111111xxxx", InstName.Uqsub8, InstEmit32.Uqsub8, OpCode32AluReg.Create);
SetA32("<<<<0110111xxxxxxxxxxxxxxx01xxxx", InstName.Usat, InstEmit32.Usat, OpCode32Sat.Create);
SetA32("<<<<01101110xxxxxxxx11110011xxxx", InstName.Usat16, InstEmit32.Usat16, OpCode32Sat16.Create);
SetA32("<<<<01100101xxxxxxxx11111111xxxx", InstName.Usub8, InstEmit32.Usub8, OpCode32AluReg.Create);
@ -872,6 +880,7 @@ namespace ARMeilleure.Decoders
SetVfp("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32);
SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110110xxxx101x01x0xxxx", InstName.Vrintr, InstEmit32.Vrintr_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110001xxxx101x11x0xxxx", InstName.Vsqrt, InstEmit32.Vsqrt_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("111111100xxxxxxxxxxx101xx0x0xxxx", InstName.Vsel, InstEmit32.Vsel, OpCode32SimdSel.Create, OpCode32SimdSel.CreateT32);
@ -992,6 +1001,7 @@ namespace ARMeilleure.Decoders
SetAsimd("1111001x1x000xxxxxxx<<x10x01xxxx", InstName.Vorr, InstEmit32.Vorr_II, OpCode32SimdImm.Create, OpCode32SimdImm.CreateT32);
SetAsimd("111100100x<<xxxxxxxx1011x0x1xxxx", InstName.Vpadd, InstEmit32.Vpadd_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100110x00xxxxxxxx1101x0x0xxxx", InstName.Vpadd, InstEmit32.Vpadd_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100111x11<<00xxxx0110xxx0xxxx", InstName.Vpadal, InstEmit32.Vpadal, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
SetAsimd("111100111x11<<00xxxx0010xxx0xxxx", InstName.Vpaddl, InstEmit32.Vpaddl, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
SetAsimd("1111001x0x<<xxxxxxxx1010x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100110x00xxxxxxxx1111x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
@ -1002,6 +1012,8 @@ namespace ARMeilleure.Decoders
SetAsimd("111100100x10xxxxxxxx1011xxx0xxxx", InstName.Vqdmulh, InstEmit32.Vqdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100111x11<<10xxxx00101xx0xxx0", InstName.Vqmovn, InstEmit32.Vqmovn, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
SetAsimd("111100111x11<<10xxxx001001x0xxx0", InstName.Vqmovun, InstEmit32.Vqmovun, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
SetAsimd("111100110x01xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100110x10xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
SetAsimd("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx100100x1xxx0", InstName.Vqshrn, InstEmit32.Vqshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
@ -1023,8 +1035,10 @@ namespace ARMeilleure.Decoders
SetAsimd("111100101x>>>xxxxxxx0101>xx1xxxx", InstName.Vshl, InstEmit32.Vshl, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
SetAsimd("1111001x0xxxxxxxxxxx0100xxx0xxxx", InstName.Vshl, InstEmit32.Vshl_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx101000x1xxxx", InstName.Vshll, InstEmit32.Vshll, OpCode32SimdShImmLong.Create, OpCode32SimdShImmLong.CreateT32); // A1 encoding.
SetAsimd("111100111x11<<10xxxx001100x0xxxx", InstName.Vshll, InstEmit32.Vshll2, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32); // A2 encoding.
SetAsimd("1111001x1x>>>xxxxxxx0000>xx1xxxx", InstName.Vshr, InstEmit32.Vshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
SetAsimd("111100101x>>>xxxxxxx100000x1xxx0", InstName.Vshrn, InstEmit32.Vshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
SetAsimd("111100111x>>>xxxxxxx0101>xx1xxxx", InstName.Vsli, InstEmit32.Vsli_I, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx0001>xx1xxxx", InstName.Vsra, InstEmit32.Vsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
SetAsimd("111101001x00xxxxxxxx0000xxx0xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
SetAsimd("111101001x00xxxxxxxx0100xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
@ -1049,6 +1063,7 @@ namespace ARMeilleure.Decoders
SetAsimd("111100100x10xxxxxxxx1101xxx0xxxx", InstName.Vsub, InstEmit32.Vsub_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("1111001x1x<<xxxxxxx00010x0x0xxxx", InstName.Vsubl, InstEmit32.Vsubl_I, OpCode32SimdRegLong.Create, OpCode32SimdRegLong.CreateT32);
SetAsimd("1111001x1x<<xxxxxxx00011x0x0xxxx", InstName.Vsubw, InstEmit32.Vsubw_I, OpCode32SimdRegWide.Create, OpCode32SimdRegWide.CreateT32);
SetAsimd("111100111x110010xxxx00000xx0xxxx", InstName.Vswp, InstEmit32.Vswp, OpCode32Simd.Create, OpCode32Simd.CreateT32);
SetAsimd("111100111x11xxxxxxxx10xxxxx0xxxx", InstName.Vtbl, InstEmit32.Vtbl, OpCode32SimdTbl.Create, OpCode32SimdTbl.CreateT32);
SetAsimd("111100111x11<<10xxxx00001xx0xxxx", InstName.Vtrn, InstEmit32.Vtrn, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
SetAsimd("111100100x<<xxxxxxxx1000xxx1xxxx", InstName.Vtst, InstEmit32.Vtst, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);

View file

@ -2,6 +2,8 @@ using ARMeilleure.Decoders;
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State;
using ARMeilleure.Translation;
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using static ARMeilleure.Instructions.InstEmitAluHelper;
using static ARMeilleure.Instructions.InstEmitHelper;
@ -19,6 +21,12 @@ namespace ARMeilleure.Instructions
Operand n = GetAluN(context);
Operand m = GetAluM(context, setCarry: false);
if (op.Rn == RegisterAlias.Aarch32Pc && op is OpCodeT32AluImm12)
{
// For ADR, PC is always 4 bytes aligned, even in Thumb mode.
n = context.BitwiseAnd(n, Const(~3u));
}
Operand res = context.Add(n, m);
if (ShouldSetFlags(context))
@ -284,6 +292,16 @@ namespace ARMeilleure.Instructions
EmitAluStore(context, res);
}
public static void Qadd16(ArmEmitterContext context)
{
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
SetIntA32(context, op.Rd, EmitSigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
{
EmitSaturateRange(context, d, context.Add(n, m), 16, unsigned: false, setQ: false);
}));
}
public static void Rbit(ArmEmitterContext context)
{
Operand m = GetAluM(context);
@ -467,6 +485,12 @@ namespace ARMeilleure.Instructions
Operand n = GetAluN(context);
Operand m = GetAluM(context, setCarry: false);
if (op.Rn == RegisterAlias.Aarch32Pc && op is OpCodeT32AluImm12)
{
// For ADR, PC is always 4 bytes aligned, even in Thumb mode.
n = context.BitwiseAnd(n, Const(~3u));
}
Operand res = context.Subtract(n, m);
if (ShouldSetFlags(context))
@ -546,6 +570,46 @@ namespace ARMeilleure.Instructions
EmitHsub8(context, unsigned: true);
}
public static void Uqadd16(ArmEmitterContext context)
{
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
SetIntA32(context, op.Rd, EmitUnsigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
{
EmitSaturateUqadd(context, d, context.Add(n, m), 16);
}));
}
public static void Uqadd8(ArmEmitterContext context)
{
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
{
EmitSaturateUqadd(context, d, context.Add(n, m), 8);
}));
}
public static void Uqsub16(ArmEmitterContext context)
{
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
SetIntA32(context, op.Rd, EmitUnsigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
{
EmitSaturateUqsub(context, d, context.Subtract(n, m), 16);
}));
}
public static void Uqsub8(ArmEmitterContext context)
{
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
{
EmitSaturateUqsub(context, d, context.Subtract(n, m), 8);
}));
}
public static void Usat(ArmEmitterContext context)
{
OpCode32Sat op = (OpCode32Sat)context.CurrOp;
@ -922,6 +986,251 @@ namespace ARMeilleure.Instructions
}
}
private static void EmitSaturateRange(ArmEmitterContext context, Operand result, Operand value, uint saturateTo, bool unsigned, bool setQ = true)
{
Debug.Assert(saturateTo <= 32);
Debug.Assert(!unsigned || saturateTo < 32);
if (!unsigned && saturateTo == 32)
{
// No saturation possible for this case.
context.Copy(result, value);
return;
}
else if (saturateTo == 0)
{
// Result is always zero if we saturate 0 bits.
context.Copy(result, Const(0));
return;
}
Operand satValue;
if (unsigned)
{
// Negative values always saturate (to zero).
// So we must always ignore the sign bit when masking, so that the truncated value will differ from the original one.
satValue = context.BitwiseAnd(value, Const((int)(uint.MaxValue >> (32 - (int)saturateTo))));
}
else
{
satValue = context.ShiftLeft(value, Const(32 - (int)saturateTo));
satValue = context.ShiftRightSI(satValue, Const(32 - (int)saturateTo));
}
// If the result is 0, the values are equal and we don't need saturation.
Operand lblNoSat = Label();
context.BranchIfFalse(lblNoSat, context.Subtract(value, satValue));
// Saturate and set Q flag.
if (unsigned)
{
if (saturateTo == 31)
{
// Only saturation case possible when going from 32 bits signed to 32 or 31 bits unsigned
// is when the signed input is negative, as all positive values are representable on a 31 bits range.
satValue = Const(0);
}
else
{
satValue = context.ShiftRightSI(value, Const(31));
satValue = context.BitwiseNot(satValue);
satValue = context.ShiftRightUI(satValue, Const(32 - (int)saturateTo));
}
}
else
{
if (saturateTo == 1)
{
satValue = context.ShiftRightSI(value, Const(31));
}
else
{
satValue = Const(uint.MaxValue >> (33 - (int)saturateTo));
satValue = context.BitwiseExclusiveOr(satValue, context.ShiftRightSI(value, Const(31)));
}
}
if (setQ)
{
SetFlag(context, PState.QFlag, Const(1));
}
context.Copy(result, satValue);
Operand lblExit = Label();
context.Branch(lblExit);
context.MarkLabel(lblNoSat);
context.Copy(result, value);
context.MarkLabel(lblExit);
}
private static void EmitSaturateUqadd(ArmEmitterContext context, Operand result, Operand value, uint saturateTo)
{
Debug.Assert(saturateTo <= 32);
if (saturateTo == 32)
{
// No saturation possible for this case.
context.Copy(result, value);
return;
}
else if (saturateTo == 0)
{
// Result is always zero if we saturate 0 bits.
context.Copy(result, Const(0));
return;
}
// If the result is 0, the values are equal and we don't need saturation.
Operand lblNoSat = Label();
context.BranchIfFalse(lblNoSat, context.ShiftRightUI(value, Const((int)saturateTo)));
// Saturate.
context.Copy(result, Const(uint.MaxValue >> (32 - (int)saturateTo)));
Operand lblExit = Label();
context.Branch(lblExit);
context.MarkLabel(lblNoSat);
context.Copy(result, value);
context.MarkLabel(lblExit);
}
private static void EmitSaturateUqsub(ArmEmitterContext context, Operand result, Operand value, uint saturateTo)
{
Debug.Assert(saturateTo <= 32);
if (saturateTo == 32)
{
// No saturation possible for this case.
context.Copy(result, value);
return;
}
else if (saturateTo == 0)
{
// Result is always zero if we saturate 0 bits.
context.Copy(result, Const(0));
return;
}
// If the result is 0, the values are equal and we don't need saturation.
Operand lblNoSat = Label();
context.BranchIf(lblNoSat, value, Const(0), Comparison.GreaterOrEqual);
// Saturate.
// Assumes that the value can only underflow, since this is only used for unsigned subtraction.
context.Copy(result, Const(0));
Operand lblExit = Label();
context.Branch(lblExit);
context.MarkLabel(lblNoSat);
context.Copy(result, value);
context.MarkLabel(lblExit);
}
private static Operand EmitSigned16BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
{
Operand tempD = context.AllocateLocal(OperandType.I32);
Operand tempN = context.SignExtend16(OperandType.I32, rn);
Operand tempM = context.SignExtend16(OperandType.I32, rm);
elementAction(tempD, tempN, tempM);
Operand tempD2 = context.ZeroExtend16(OperandType.I32, tempD);
tempN = context.ShiftRightSI(rn, Const(16));
tempM = context.ShiftRightSI(rm, Const(16));
elementAction(tempD, tempN, tempM);
return context.BitwiseOr(tempD2, context.ShiftLeft(tempD, Const(16)));
}
private static Operand EmitUnsigned16BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
{
Operand tempD = context.AllocateLocal(OperandType.I32);
Operand tempN = context.ZeroExtend16(OperandType.I32, rn);
Operand tempM = context.ZeroExtend16(OperandType.I32, rm);
elementAction(tempD, tempN, tempM);
Operand tempD2 = context.ZeroExtend16(OperandType.I32, tempD);
tempN = context.ShiftRightUI(rn, Const(16));
tempM = context.ShiftRightUI(rm, Const(16));
elementAction(tempD, tempN, tempM);
return context.BitwiseOr(tempD2, context.ShiftLeft(tempD, Const(16)));
}
private static Operand EmitSigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
{
return Emit8BitPair(context, rn, rm, elementAction, unsigned: false);
}
private static Operand EmitUnsigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
{
return Emit8BitPair(context, rn, rm, elementAction, unsigned: true);
}
private static Operand Emit8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction, bool unsigned)
{
Operand tempD = context.AllocateLocal(OperandType.I32);
Operand result = default;
for (int b = 0; b < 4; b++)
{
Operand nByte = b != 0 ? context.ShiftRightUI(rn, Const(b * 8)) : rn;
Operand mByte = b != 0 ? context.ShiftRightUI(rm, Const(b * 8)) : rm;
if (unsigned)
{
nByte = context.ZeroExtend8(OperandType.I32, nByte);
mByte = context.ZeroExtend8(OperandType.I32, mByte);
}
else
{
nByte = context.SignExtend8(OperandType.I32, nByte);
mByte = context.SignExtend8(OperandType.I32, mByte);
}
elementAction(tempD, nByte, mByte);
if (b == 0)
{
result = context.ZeroExtend8(OperandType.I32, tempD);
}
else if (b < 3)
{
result = context.BitwiseOr(result, context.ShiftLeft(context.ZeroExtend8(OperandType.I32, tempD), Const(b * 8)));
}
else
{
result = context.BitwiseOr(result, context.ShiftLeft(tempD, Const(24)));
}
}
return result;
}
private static void EmitAluStore(ArmEmitterContext context, Operand value)
{
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;

View file

@ -193,6 +193,8 @@ namespace ARMeilleure.Instructions
Operand hostAddress;
var table = context.FunctionTable;
// If address is mapped onto the function table, we can skip the table walk. Otherwise we fallback
// onto the dispatch stub.
if (guestAddress.Kind == OperandKind.Constant && context.FunctionTable.IsValid(guestAddress.Value))
@ -203,6 +205,30 @@ namespace ARMeilleure.Instructions
hostAddress = context.Load(OperandType.I64, hostAddressAddr);
}
else if (table.Sparse)
{
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
// Deliberately attempts to avoid branches.
Operand tableBase = !context.HasPtc ?
Const(table.Base) :
Const(table.Base, Ptc.FunctionTableSymbol);
hostAddress = tableBase;
for (int i = 0; i < table.Levels.Length; i++)
{
var level = table.Levels[i];
int clearBits = 64 - (level.Index + level.Length);
Operand index = context.ShiftLeft(
context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits)), Const(clearBits + level.Index)),
Const(3)
);
hostAddress = context.Load(OperandType.I64, context.Add(hostAddress, index));
}
}
else
{
hostAddress = !context.HasPtc ?

View file

@ -403,19 +403,25 @@ namespace ARMeilleure.Instructions
{
return EmitHostMappedPointer(context, address);
}
else if (context.Memory.Type == MemoryManagerType.HostTracked)
else if (context.Memory.Type.IsHostTracked())
{
if (address.Type == OperandType.I32)
{
address = context.ZeroExtend32(OperandType.I64, address);
}
if (context.Memory.Type == MemoryManagerType.HostTracked)
{
Operand mask = Const(ulong.MaxValue >> (64 - context.Memory.AddressSpaceBits));
address = context.BitwiseAnd(address, mask);
}
Operand ptBase = !context.HasPtc
? Const(context.Memory.PageTablePointer.ToInt64())
: Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol);
Operand ptOffset = context.ShiftRightUI(address, Const(PageBits));
if (ptOffset.Type == OperandType.I32)
{
ptOffset = context.ZeroExtend32(OperandType.I64, ptOffset);
}
return context.Add(address, context.Load(OperandType.I64, context.Add(ptBase, context.ShiftLeft(ptOffset, Const(3)))));
}

View file

@ -2426,7 +2426,11 @@ namespace ARMeilleure.Instructions
}
else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0)
{
Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtss, GetVec(op.Rn)), scalar: true);
// RSQRTSS handles subnormals as zero, which differs from Arm, so we can't use it here.
Operand res = context.AddIntrinsic(Intrinsic.X86Sqrtss, GetVec(op.Rn));
res = context.AddIntrinsic(Intrinsic.X86Rcpss, res);
res = EmitSse41Round32Exp8OpF(context, res, scalar: true);
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
}
@ -2451,7 +2455,11 @@ namespace ARMeilleure.Instructions
}
else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0)
{
Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtps, GetVec(op.Rn)), scalar: false);
// RSQRTPS handles subnormals as zero, which differs from Arm, so we can't use it here.
Operand res = context.AddIntrinsic(Intrinsic.X86Sqrtps, GetVec(op.Rn));
res = context.AddIntrinsic(Intrinsic.X86Rcpps, res);
res = EmitSse41Round32Exp8OpF(context, res, scalar: false);
if (op.RegisterSize == RegisterSize.Simd64)
{

View file

@ -1115,6 +1115,13 @@ namespace ARMeilleure.Instructions
}
}
public static void Vpadal(ArmEmitterContext context)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
EmitVectorPairwiseTernaryLongOpI32(context, (op1, op2, op3) => context.Add(context.Add(op1, op2), op3), op.Opc != 1);
}
public static void Vpaddl(ArmEmitterContext context)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
@ -1239,6 +1246,33 @@ namespace ARMeilleure.Instructions
EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signedSrc: true, signedDst: false), signed: true);
}
public static void Vqrdmulh(ArmEmitterContext context)
{
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
int eSize = 8 << op.Size;
EmitVectorBinaryOpI32(context, (op1, op2) =>
{
if (op.Size == 2)
{
op1 = context.SignExtend32(OperandType.I64, op1);
op2 = context.SignExtend32(OperandType.I64, op2);
}
Operand res = context.Multiply(op1, op2);
res = context.Add(res, Const(res.Type, 1L << (eSize - 2)));
res = context.ShiftRightSI(res, Const(eSize - 1));
res = EmitSatQ(context, res, eSize, signedSrc: true, signedDst: true);
if (op.Size == 2)
{
res = context.ConvertI64ToI32(res);
}
return res;
}, signed: true);
}
public static void Vqsub(ArmEmitterContext context)
{
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;

View file

@ -578,6 +578,22 @@ namespace ARMeilleure.Instructions
}
}
// VRINTR (floating-point).
public static void Vrintr_S(ArmEmitterContext context)
{
if (Optimizations.UseAdvSimd)
{
InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintiS);
}
else
{
EmitScalarUnaryOpF32(context, (op1) =>
{
return EmitRoundByRMode(context, op1);
});
}
}
// VRINTZ (floating-point).
public static void Vrint_Z(ArmEmitterContext context)
{

View file

@ -673,6 +673,35 @@ namespace ARMeilleure.Instructions
context.Copy(GetVecA32(op.Qd), res);
}
public static void EmitVectorPairwiseTernaryLongOpI32(ArmEmitterContext context, Func3I emit, bool signed)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
int elems = op.GetBytesCount() >> op.Size;
int pairs = elems >> 1;
Operand res = GetVecA32(op.Qd);
for (int index = 0; index < pairs; index++)
{
int pairIndex = index * 2;
Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed);
Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed);
if (op.Size == 2)
{
m1 = signed ? context.SignExtend32(OperandType.I64, m1) : context.ZeroExtend32(OperandType.I64, m1);
m2 = signed ? context.SignExtend32(OperandType.I64, m2) : context.ZeroExtend32(OperandType.I64, m2);
}
Operand d1 = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size + 1, signed);
res = EmitVectorInsert(context, res, emit(m1, m2, d1), op.Id + index, op.Size + 1);
}
context.Copy(GetVecA32(op.Qd), res);
}
// Narrow
public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false)

View file

@ -191,6 +191,26 @@ namespace ARMeilleure.Instructions
context.Copy(GetVecA32(op.Qd), res);
}
public static void Vswp(ArmEmitterContext context)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
if (op.Q)
{
Operand temp = context.Copy(GetVecA32(op.Qd));
context.Copy(GetVecA32(op.Qd), GetVecA32(op.Qm));
context.Copy(GetVecA32(op.Qm), temp);
}
else
{
Operand temp = ExtractScalar(context, OperandType.I64, op.Vd);
InsertScalar(context, op.Vd, ExtractScalar(context, OperandType.I64, op.Vm));
InsertScalar(context, op.Vm, temp);
}
}
public static void Vtbl(ArmEmitterContext context)
{
OpCode32SimdTbl op = (OpCode32SimdTbl)context.CurrOp;

View file

@ -116,7 +116,7 @@ namespace ARMeilleure.Instructions
}
else if (shift >= eSize)
{
if ((op.RegisterSize == RegisterSize.Simd64))
if (op.RegisterSize == RegisterSize.Simd64)
{
Operand res = context.VectorZeroUpper64(GetVec(op.Rd));
@ -359,6 +359,16 @@ namespace ARMeilleure.Instructions
}
}
public static void Sqshl_Si(ArmEmitterContext context)
{
EmitShlImmOp(context, signedDst: true, ShlRegFlags.Signed | ShlRegFlags.Scalar | ShlRegFlags.Saturating);
}
public static void Sqshl_Vi(ArmEmitterContext context)
{
EmitShlImmOp(context, signedDst: true, ShlRegFlags.Signed | ShlRegFlags.Saturating);
}
public static void Sqshrn_S(ArmEmitterContext context)
{
if (Optimizations.UseAdvSimd)
@ -1593,6 +1603,99 @@ namespace ARMeilleure.Instructions
Saturating = 1 << 3,
}
private static void EmitShlImmOp(ArmEmitterContext context, bool signedDst, ShlRegFlags flags = ShlRegFlags.None)
{
bool scalar = flags.HasFlag(ShlRegFlags.Scalar);
bool signed = flags.HasFlag(ShlRegFlags.Signed);
bool saturating = flags.HasFlag(ShlRegFlags.Saturating);
OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp;
Operand res = context.VectorZero();
int elems = !scalar ? op.GetBytesCount() >> op.Size : 1;
for (int index = 0; index < elems; index++)
{
Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed);
Operand e = !saturating
? EmitShlImm(context, ne, GetImmShl(op), op.Size)
: EmitShlImmSatQ(context, ne, GetImmShl(op), op.Size, signed, signedDst);
res = EmitVectorInsert(context, res, e, index, op.Size);
}
context.Copy(GetVec(op.Rd), res);
}
private static Operand EmitShlImm(ArmEmitterContext context, Operand op, int shiftLsB, int size)
{
int eSize = 8 << size;
Debug.Assert(op.Type == OperandType.I64);
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64);
Operand res = context.AllocateLocal(OperandType.I64);
if (shiftLsB >= eSize)
{
Operand shl = context.ShiftLeft(op, Const(shiftLsB));
context.Copy(res, shl);
}
else
{
Operand zeroL = Const(0L);
context.Copy(res, zeroL);
}
return res;
}
private static Operand EmitShlImmSatQ(ArmEmitterContext context, Operand op, int shiftLsB, int size, bool signedSrc, bool signedDst)
{
int eSize = 8 << size;
Debug.Assert(op.Type == OperandType.I64);
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64);
Operand lblEnd = Label();
Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op);
if (shiftLsB >= eSize)
{
context.Copy(res, signedSrc
? EmitSignedSignSatQ(context, op, size)
: EmitUnsignedSignSatQ(context, op, size));
}
else
{
Operand shl = context.ShiftLeft(op, Const(shiftLsB));
if (eSize == 64)
{
Operand sarOrShr = signedSrc
? context.ShiftRightSI(shl, Const(shiftLsB))
: context.ShiftRightUI(shl, Const(shiftLsB));
context.Copy(res, shl);
context.BranchIf(lblEnd, sarOrShr, op, Comparison.Equal);
context.Copy(res, signedSrc
? EmitSignedSignSatQ(context, op, size)
: EmitUnsignedSignSatQ(context, op, size));
}
else
{
context.Copy(res, signedSrc
? EmitSignedSrcSatQ(context, shl, size, signedDst)
: EmitUnsignedSrcSatQ(context, shl, size, signedDst));
}
}
context.MarkLabel(lblEnd);
return res;
}
private static void EmitShlRegOp(ArmEmitterContext context, ShlRegFlags flags = ShlRegFlags.None)
{
bool scalar = flags.HasFlag(ShlRegFlags.Scalar);

View file

@ -106,6 +106,38 @@ namespace ARMeilleure.Instructions
context.Copy(GetVecA32(op.Qd), res);
}
public static void Vshll2(ArmEmitterContext context)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
Operand res = context.VectorZero();
int elems = op.GetBytesCount() >> op.Size;
for (int index = 0; index < elems; index++)
{
Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, !op.U);
if (op.Size == 2)
{
if (op.U)
{
me = context.ZeroExtend32(OperandType.I64, me);
}
else
{
me = context.SignExtend32(OperandType.I64, me);
}
}
me = context.ShiftLeft(me, Const(8 << op.Size));
res = EmitVectorInsert(context, res, me, index, op.Size + 1);
}
context.Copy(GetVecA32(op.Qd), res);
}
public static void Vshr(ArmEmitterContext context)
{
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
@ -130,6 +162,36 @@ namespace ARMeilleure.Instructions
EmitVectorUnaryNarrowOp32(context, (op1) => context.ShiftRightUI(op1, Const(shift)));
}
public static void Vsli_I(ArmEmitterContext context)
{
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
int shift = op.Shift;
int eSize = 8 << op.Size;
ulong mask = shift != 0 ? ulong.MaxValue >> (64 - shift) : 0UL;
Operand res = GetVec(op.Qd);
int elems = op.GetBytesCount() >> op.Size;
for (int index = 0; index < elems; index++)
{
Operand me = EmitVectorExtractZx(context, op.Qm, op.Im + index, op.Size);
Operand neShifted = context.ShiftLeft(me, Const(shift));
Operand de = EmitVectorExtractZx(context, op.Qd, op.Id + index, op.Size);
Operand deMasked = context.BitwiseAnd(de, Const(mask));
Operand e = context.BitwiseOr(neShifted, deMasked);
res = EmitVectorInsert(context, res, e, op.Id + index, op.Size);
}
context.Copy(GetVec(op.Qd), res);
}
public static void Vsra(ArmEmitterContext context)
{
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;

View file

@ -49,6 +49,9 @@ namespace ARMeilleure.Instructions
case 0b11_011_1101_0000_011:
EmitGetTpidrroEl0(context);
return;
case 0b11_011_1101_0000_101:
EmitGetTpidr2El0(context);
return;
case 0b11_011_1110_0000_000:
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0));
break;
@ -84,6 +87,9 @@ namespace ARMeilleure.Instructions
case 0b11_011_1101_0000_010:
EmitSetTpidrEl0(context);
return;
case 0b11_011_1101_0000_101:
EmitGetTpidr2El0(context);
return;
default:
throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
@ -213,6 +219,17 @@ namespace ARMeilleure.Instructions
SetIntOrZR(context, op.Rt, result);
}
private static void EmitGetTpidr2El0(ArmEmitterContext context)
{
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidr2El0Offset())));
SetIntOrZR(context, op.Rt, result);
}
private static void EmitSetNzcv(ArmEmitterContext context)
{
OpCodeSystem op = (OpCodeSystem)context.CurrOp;

View file

@ -384,7 +384,9 @@ namespace ARMeilleure.Instructions
Sqrshrn_V,
Sqrshrun_S,
Sqrshrun_V,
Sqshl_Si,
Sqshl_V,
Sqshl_Vi,
Sqshrn_S,
Sqshrn_V,
Sqshrun_S,
@ -525,6 +527,7 @@ namespace ARMeilleure.Instructions
Pld,
Pop,
Push,
Qadd16,
Rev,
Revsh,
Rsb,
@ -569,6 +572,10 @@ namespace ARMeilleure.Instructions
Umaal,
Umlal,
Umull,
Uqadd16,
Uqadd8,
Uqsub16,
Uqsub8,
Usat,
Usat16,
Usub8,
@ -635,6 +642,7 @@ namespace ARMeilleure.Instructions
Vorn,
Vorr,
Vpadd,
Vpadal,
Vpaddl,
Vpmax,
Vpmin,
@ -642,6 +650,7 @@ namespace ARMeilleure.Instructions
Vqdmulh,
Vqmovn,
Vqmovun,
Vqrdmulh,
Vqrshrn,
Vqrshrun,
Vqshrn,
@ -654,6 +663,7 @@ namespace ARMeilleure.Instructions
Vrintm,
Vrintn,
Vrintp,
Vrintr,
Vrintx,
Vrshr,
Vrshrn,
@ -662,6 +672,7 @@ namespace ARMeilleure.Instructions
Vshll,
Vshr,
Vshrn,
Vsli,
Vst1,
Vst2,
Vst3,
@ -678,6 +689,7 @@ namespace ARMeilleure.Instructions
Vsub,
Vsubl,
Vsubw,
Vswp,
Vtbl,
Vtrn,
Vtst,

View file

@ -91,54 +91,54 @@ namespace ARMeilleure.Instructions
#region "Read"
public static byte ReadByte(ulong address)
{
return GetMemoryManager().ReadTracked<byte>(address);
return GetMemoryManager().ReadGuest<byte>(address);
}
public static ushort ReadUInt16(ulong address)
{
return GetMemoryManager().ReadTracked<ushort>(address);
return GetMemoryManager().ReadGuest<ushort>(address);
}
public static uint ReadUInt32(ulong address)
{
return GetMemoryManager().ReadTracked<uint>(address);
return GetMemoryManager().ReadGuest<uint>(address);
}
public static ulong ReadUInt64(ulong address)
{
return GetMemoryManager().ReadTracked<ulong>(address);
return GetMemoryManager().ReadGuest<ulong>(address);
}
public static V128 ReadVector128(ulong address)
{
return GetMemoryManager().ReadTracked<V128>(address);
return GetMemoryManager().ReadGuest<V128>(address);
}
#endregion
#region "Write"
public static void WriteByte(ulong address, byte value)
{
GetMemoryManager().Write(address, value);
GetMemoryManager().WriteGuest(address, value);
}
public static void WriteUInt16(ulong address, ushort value)
{
GetMemoryManager().Write(address, value);
GetMemoryManager().WriteGuest(address, value);
}
public static void WriteUInt32(ulong address, uint value)
{
GetMemoryManager().Write(address, value);
GetMemoryManager().WriteGuest(address, value);
}
public static void WriteUInt64(ulong address, ulong value)
{
GetMemoryManager().Write(address, value);
GetMemoryManager().WriteGuest(address, value);
}
public static void WriteVector128(ulong address, V128 value)
{
GetMemoryManager().Write(address, value);
GetMemoryManager().WriteGuest(address, value);
}
#endregion

View file

@ -32,7 +32,7 @@ namespace ARMeilleure.IntermediateRepresentation
/// <exception cref="ArgumentException"><typeparamref name="T"/> is not pointer sized.</exception>
public IntrusiveList()
{
if (Unsafe.SizeOf<T>() != IntPtr.Size)
if (Unsafe.SizeOf<T>() != nint.Size)
{
throw new ArgumentException("T must be a reference type or a pointer sized struct.");
}

View file

@ -24,7 +24,7 @@ namespace ARMeilleure.IntermediateRepresentation
{
Debug.Assert(operand.Kind == OperandKind.Memory);
_data = (Data*)Unsafe.As<Operand, IntPtr>(ref operand);
_data = (Data*)Unsafe.As<Operand, nint>(ref operand);
}
public Operand BaseAddress

View file

@ -228,7 +228,7 @@ namespace ARMeilleure.IntermediateRepresentation
public readonly override int GetHashCode()
{
return HashCode.Combine((IntPtr)_data);
return HashCode.Combine((nint)_data);
}
public static bool operator ==(Operation a, Operation b)

View file

@ -4,7 +4,5 @@ namespace ARMeilleure.Memory
{
IJitMemoryBlock Allocate(ulong size);
IJitMemoryBlock Reserve(ulong size);
ulong GetPageSize();
}
}

View file

@ -4,7 +4,7 @@ namespace ARMeilleure.Memory
{
public interface IJitMemoryBlock : IDisposable
{
IntPtr Pointer { get; }
nint Pointer { get; }
void Commit(ulong offset, ulong size);

View file

@ -6,7 +6,7 @@ namespace ARMeilleure.Memory
{
int AddressSpaceBits { get; }
IntPtr PageTablePointer { get; }
nint PageTablePointer { get; }
MemoryManagerType Type { get; }
@ -28,6 +28,17 @@ namespace ARMeilleure.Memory
/// <returns>The data</returns>
T ReadTracked<T>(ulong va) where T : unmanaged;
/// <summary>
/// Reads data from CPU mapped memory, from guest code. (with read tracking)
/// </summary>
/// <typeparam name="T">Type of the data being read</typeparam>
/// <param name="va">Virtual address of the data in memory</param>
/// <returns>The data</returns>
T ReadGuest<T>(ulong va) where T : unmanaged
{
return ReadTracked<T>(va);
}
/// <summary>
/// Writes data to CPU mapped memory.
/// </summary>
@ -36,6 +47,17 @@ namespace ARMeilleure.Memory
/// <param name="value">Data to be written</param>
void Write<T>(ulong va, T value) where T : unmanaged;
/// <summary>
/// Writes data to CPU mapped memory, from guest code.
/// </summary>
/// <typeparam name="T">Type of the data being written</typeparam>
/// <param name="va">Virtual address to write the data into</param>
/// <param name="value">Data to be written</param>
void WriteGuest<T>(ulong va, T value) where T : unmanaged
{
Write(va, value);
}
/// <summary>
/// Gets a read-only span of data from CPU mapped memory.
/// </summary>

View file

@ -18,12 +18,6 @@ namespace ARMeilleure.Memory
/// </summary>
SoftwarePageTable,
/// <summary>
/// High level implementation using a software flat page table for address translation,
/// no support for handling invalid or non-contiguous memory access.
/// </summary>
HostTracked,
/// <summary>
/// High level implementation with mappings managed by the host OS, effectively using hardware
/// page tables. No address translation is performed in software and the memory is just accessed directly.
@ -35,18 +29,35 @@ namespace ARMeilleure.Memory
/// Allows invalid access from JIT code to the rest of the program, but is faster.
/// </summary>
HostMappedUnsafe,
/// <summary>
/// High level implementation using a software flat page table for address translation
/// with no support for handling invalid or non-contiguous memory access.
/// </summary>
HostTracked,
/// <summary>
/// High level implementation using a software flat page table for address translation
/// without masking the address and no support for handling invalid or non-contiguous memory access.
/// </summary>
HostTrackedUnsafe,
}
static class MemoryManagerTypeExtensions
public static class MemoryManagerTypeExtensions
{
public static bool IsHostMapped(this MemoryManagerType type)
{
return type == MemoryManagerType.HostMapped || type == MemoryManagerType.HostMappedUnsafe;
}
public static bool IsHostTracked(this MemoryManagerType type)
{
return type == MemoryManagerType.HostTracked || type == MemoryManagerType.HostTrackedUnsafe;
}
public static bool IsHostMappedOrTracked(this MemoryManagerType type)
{
return type == MemoryManagerType.HostTracked || type == MemoryManagerType.HostMapped || type == MemoryManagerType.HostMappedUnsafe;
return type.IsHostMapped() || type.IsHostTracked();
}
}
}

View file

@ -8,7 +8,7 @@ namespace ARMeilleure.Memory
public IJitMemoryBlock Block { get; }
public IntPtr Pointer => Block.Pointer;
public nint Pointer => Block.Pointer;
private readonly ulong _maxSize;
private readonly ulong _sizeGranularity;

View file

@ -5,41 +5,9 @@ using System.Runtime.Versioning;
namespace ARMeilleure.Native
{
[SupportedOSPlatform("macos")]
[SupportedOSPlatform("ios")]
static partial class JitSupportDarwin
{
[LibraryImport("libarmeilleure-jitsupport", EntryPoint = "armeilleure_jit_memcpy")]
public static partial void Copy(IntPtr dst, IntPtr src, ulong n);
}
[SupportedOSPlatform("ios")]
internal static partial class JitSupportDarwinAot
{
[LibraryImport("pthread", EntryPoint = "pthread_jit_write_protect_np")]
private static partial void pthread_jit_write_protect_np(int enabled);
[LibraryImport("libc", EntryPoint = "sys_icache_invalidate")]
private static partial void sys_icache_invalidate(IntPtr start, IntPtr length);
public static unsafe void Copy(IntPtr dst, IntPtr src, ulong n) {
// When NativeAOT is in use, we can toggle per-thread write protection without worrying about breaking .NET code.
//pthread_jit_write_protect_np(0);
var srcSpan = new Span<byte>(src.ToPointer(), (int)n);
var dstSpan = new Span<byte>(dst.ToPointer(), (int)n);
srcSpan.CopyTo(dstSpan);
//pthread_jit_write_protect_np(1);
// Ensure that the instruction cache for this range is invalidated.
sys_icache_invalidate(dst, (IntPtr)n);
}
public static unsafe void Invalidate(IntPtr dst, ulong n)
{
// Ensure that the instruction cache for this range is invalidated.
sys_icache_invalidate(dst, (IntPtr)n);
}
public static partial void Copy(nint dst, nint src, ulong n);
}
}

View file

@ -5,6 +5,9 @@ namespace ARMeilleure
public static class Optimizations
{
// low-core count PPTC
public static bool LowPower { get; set; } = false;
public static bool FastFP { get; set; } = true;
public static bool AllowLcqInFunctionTable { get; set; } = true;
@ -51,8 +54,8 @@ namespace ARMeilleure
internal static bool UseSse41 => UseSse41IfAvailable && X86HardwareCapabilities.SupportsSse41;
internal static bool UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42;
internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt;
internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse;
internal static bool UseAvx512F => UseAvx512FIfAvailable && X86HardwareCapabilities.SupportsAvx512F && !ForceLegacySse;
internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse;
internal static bool UseAvx512F => UseAvx512FIfAvailable && X86HardwareCapabilities.SupportsAvx512F && !ForceLegacySse;
internal static bool UseAvx512Vl => UseAvx512VlIfAvailable && X86HardwareCapabilities.SupportsAvx512Vl && !ForceLegacySse;
internal static bool UseAvx512Bw => UseAvx512BwIfAvailable && X86HardwareCapabilities.SupportsAvx512Bw && !ForceLegacySse;
internal static bool UseAvx512Dq => UseAvx512DqIfAvailable && X86HardwareCapabilities.SupportsAvx512Dq && !ForceLegacySse;

View file

@ -1,63 +1,14 @@
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Memory;
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Translation;
using ARMeilleure.Translation.Cache;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
namespace ARMeilleure.Signal
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct SignalHandlerRange
public static class NativeSignalHandlerGenerator
{
public int IsActive;
public nuint RangeAddress;
public nuint RangeEndAddress;
public IntPtr ActionPointer;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct SignalHandlerConfig
{
/// <summary>
/// The byte offset of the faulting address in the SigInfo or ExceptionRecord struct.
/// </summary>
public int StructAddressOffset;
/// <summary>
/// The byte offset of the write flag in the SigInfo or ExceptionRecord struct.
/// </summary>
public int StructWriteOffset;
/// <summary>
/// The sigaction handler that was registered before this one. (unix only)
/// </summary>
public nuint UnixOldSigaction;
/// <summary>
/// The type of the previous sigaction. True for the 3 argument variant. (unix only)
/// </summary>
public int UnixOldSigaction3Arg;
public SignalHandlerRange Range0;
public SignalHandlerRange Range1;
public SignalHandlerRange Range2;
public SignalHandlerRange Range3;
public SignalHandlerRange Range4;
public SignalHandlerRange Range5;
public SignalHandlerRange Range6;
public SignalHandlerRange Range7;
}
public static class NativeSignalHandler
{
private delegate void UnixExceptionHandler(int sig, IntPtr info, IntPtr ucontext);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate int VectoredExceptionHandler(IntPtr exceptionInfo);
private const int MaxTrackedRanges = 8;
public const int MaxTrackedRanges = 16;
private const int StructAddressOffset = 0;
private const int StructWriteOffset = 4;
@ -70,124 +21,7 @@ namespace ARMeilleure.Signal
private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005;
private static ulong _pageSize;
private static ulong _pageMask;
private static readonly IntPtr _handlerConfig;
private static IntPtr _signalHandlerPtr;
private static IntPtr _signalHandlerHandle;
private static readonly object _lock = new();
private static bool _initialized;
static NativeSignalHandler()
{
_handlerConfig = Marshal.AllocHGlobal(Unsafe.SizeOf<SignalHandlerConfig>());
ref SignalHandlerConfig config = ref GetConfigRef();
config = new SignalHandlerConfig();
}
public static void Initialize(IJitMemoryAllocator allocator)
{
JitCache.Initialize(allocator);
}
public static void InitializeSignalHandler(ulong pageSize, Func<IntPtr, IntPtr, IntPtr> customSignalHandlerFactory = null)
{
if (_initialized)
{
return;
}
lock (_lock)
{
if (_initialized)
{
return;
}
_pageSize = pageSize;
_pageMask = pageSize - 1;
ref SignalHandlerConfig config = ref GetConfigRef();
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
{
_signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateUnixSignalHandler(_handlerConfig));
if (customSignalHandlerFactory != null)
{
_signalHandlerPtr = customSignalHandlerFactory(UnixSignalHandlerRegistration.GetSegfaultExceptionHandler().sa_handler, _signalHandlerPtr);
}
var old = UnixSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr);
config.UnixOldSigaction = (nuint)(ulong)old.sa_handler;
config.UnixOldSigaction3Arg = old.sa_flags & 4;
}
else
{
config.StructAddressOffset = 40; // ExceptionInformation1
config.StructWriteOffset = 32; // ExceptionInformation0
_signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateWindowsSignalHandler(_handlerConfig));
if (customSignalHandlerFactory != null)
{
_signalHandlerPtr = customSignalHandlerFactory(IntPtr.Zero, _signalHandlerPtr);
}
_signalHandlerHandle = WindowsSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr);
}
_initialized = true;
}
}
private static unsafe ref SignalHandlerConfig GetConfigRef()
{
return ref Unsafe.AsRef<SignalHandlerConfig>((void*)_handlerConfig);
}
public static unsafe bool AddTrackedRegion(nuint address, nuint endAddress, IntPtr action)
{
var ranges = &((SignalHandlerConfig*)_handlerConfig)->Range0;
for (int i = 0; i < MaxTrackedRanges; i++)
{
if (ranges[i].IsActive == 0)
{
ranges[i].RangeAddress = address;
ranges[i].RangeEndAddress = endAddress;
ranges[i].ActionPointer = action;
ranges[i].IsActive = 1;
return true;
}
}
return false;
}
public static unsafe bool RemoveTrackedRegion(nuint address)
{
var ranges = &((SignalHandlerConfig*)_handlerConfig)->Range0;
for (int i = 0; i < MaxTrackedRanges; i++)
{
if (ranges[i].IsActive == 1 && ranges[i].RangeAddress == address)
{
ranges[i].IsActive = 0;
return true;
}
}
return false;
}
private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr signalStructPtr, Operand faultAddress, Operand isWrite)
private static Operand EmitGenericRegionCheck(EmitterContext context, nint signalStructPtr, Operand faultAddress, Operand isWrite, int rangeStructSize)
{
Operand inRegionLocal = context.AllocateLocal(OperandType.I32);
context.Copy(inRegionLocal, Const(0));
@ -196,7 +30,7 @@ namespace ARMeilleure.Signal
for (int i = 0; i < MaxTrackedRanges; i++)
{
ulong rangeBaseOffset = (ulong)(RangeOffset + i * Unsafe.SizeOf<SignalHandlerRange>());
ulong rangeBaseOffset = (ulong)(RangeOffset + i * rangeStructSize);
Operand nextLabel = Label();
@ -210,13 +44,12 @@ namespace ARMeilleure.Signal
// Is the fault address within this tracked region?
Operand inRange = context.BitwiseAnd(
context.ICompare(faultAddress, rangeAddress, Comparison.GreaterOrEqualUI),
context.ICompare(faultAddress, rangeEndAddress, Comparison.LessUI)
);
context.ICompare(faultAddress, rangeEndAddress, Comparison.LessUI));
// Only call tracking if in range.
context.BranchIfFalse(nextLabel, inRange, BasicBlockFrequency.Cold);
Operand offset = context.BitwiseAnd(context.Subtract(faultAddress, rangeAddress), Const(~_pageMask));
Operand offset = context.Subtract(faultAddress, rangeAddress);
// Call the tracking action, with the pointer's relative offset to the base address.
Operand trackingActionPtr = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 20));
@ -227,8 +60,10 @@ namespace ARMeilleure.Signal
// Tracking action should be non-null to call it, otherwise assume false return.
context.BranchIfFalse(skipActionLabel, trackingActionPtr);
Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(_pageSize), isWrite);
context.Copy(inRegionLocal, result);
Operand result = context.Call(trackingActionPtr, OperandType.I64, offset, Const(1UL), isWrite);
context.Copy(inRegionLocal, context.ICompareNotEqual(result, Const(0UL)));
GenerateFaultAddressPatchCode(context, faultAddress, result);
context.MarkLabel(skipActionLabel);
@ -252,13 +87,13 @@ namespace ARMeilleure.Signal
private static Operand GenerateUnixFaultAddress(EmitterContext context, Operand sigInfoPtr)
{
ulong structAddressOffset = (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) ? 24ul : 16ul; // si_addr
ulong structAddressOffset = OperatingSystem.IsMacOS() ? 24ul : 16ul; // si_addr
return context.Load(OperandType.I64, context.Add(sigInfoPtr, Const(structAddressOffset)));
}
private static Operand GenerateUnixWriteFlag(EmitterContext context, Operand ucontextPtr)
{
if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
if (OperatingSystem.IsMacOS())
{
const ulong McontextOffset = 48; // uc_mcontext
Operand ctxPtr = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(McontextOffset)));
@ -269,8 +104,7 @@ namespace ARMeilleure.Signal
Operand esr = context.Load(OperandType.I64, context.Add(ctxPtr, Const(EsrOffset)));
return context.BitwiseAnd(esr, Const(0x40ul));
}
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
{
const ulong ErrOffset = 4; // __es.__err
Operand err = context.Load(OperandType.I64, context.Add(ctxPtr, Const(ErrOffset)));
@ -310,8 +144,7 @@ namespace ARMeilleure.Signal
Operand esr = context.Load(OperandType.I64, context.Add(auxPtr, Const(8ul)));
return context.BitwiseAnd(esr, Const(0x40ul));
}
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
{
const int ErrOffset = 192; // uc_mcontext.gregs[REG_ERR]
Operand err = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(ErrOffset)));
@ -322,7 +155,7 @@ namespace ARMeilleure.Signal
throw new PlatformNotSupportedException();
}
private static UnixExceptionHandler GenerateUnixSignalHandler(IntPtr signalStructPtr)
public static byte[] GenerateUnixSignalHandler(nint signalStructPtr, int rangeStructSize)
{
EmitterContext context = new();
@ -335,7 +168,7 @@ namespace ARMeilleure.Signal
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite);
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize);
Operand endLabel = Label();
@ -367,10 +200,10 @@ namespace ARMeilleure.Signal
OperandType[] argTypes = new OperandType[] { OperandType.I32, OperandType.I64, OperandType.I64 };
return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<UnixExceptionHandler>();
return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Code;
}
private static VectoredExceptionHandler GenerateWindowsSignalHandler(IntPtr signalStructPtr)
public static byte[] GenerateWindowsSignalHandler(nint signalStructPtr, int rangeStructSize)
{
EmitterContext context = new();
@ -399,7 +232,7 @@ namespace ARMeilleure.Signal
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite);
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize);
Operand endLabel = Label();
@ -421,7 +254,88 @@ namespace ARMeilleure.Signal
OperandType[] argTypes = new OperandType[] { OperandType.I64 };
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<VectoredExceptionHandler>();
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Code;
}
private static void GenerateFaultAddressPatchCode(EmitterContext context, Operand faultAddress, Operand newAddress)
{
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
if (SupportsFaultAddressPatchingForHostOs())
{
Operand lblSkip = Label();
context.BranchIf(lblSkip, faultAddress, newAddress, Comparison.Equal);
Operand ucontextPtr = context.LoadArgument(OperandType.I64, 2);
Operand pcCtxAddress = default;
ulong baseRegsOffset = 0;
if (OperatingSystem.IsLinux())
{
pcCtxAddress = context.Add(ucontextPtr, Const(440UL));
baseRegsOffset = 184UL;
}
else if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
{
ucontextPtr = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(48UL)));
pcCtxAddress = context.Add(ucontextPtr, Const(272UL));
baseRegsOffset = 16UL;
}
Operand pc = context.Load(OperandType.I64, pcCtxAddress);
Operand reg = GetAddressRegisterFromArm64Instruction(context, pc);
Operand reg64 = context.ZeroExtend32(OperandType.I64, reg);
Operand regCtxAddress = context.Add(ucontextPtr, context.Add(context.ShiftLeft(reg64, Const(3)), Const(baseRegsOffset)));
Operand regAddress = context.Load(OperandType.I64, regCtxAddress);
Operand addressDelta = context.Subtract(regAddress, faultAddress);
context.Store(regCtxAddress, context.Add(newAddress, addressDelta));
context.MarkLabel(lblSkip);
}
}
}
private static Operand GetAddressRegisterFromArm64Instruction(EmitterContext context, Operand pc)
{
Operand inst = context.Load(OperandType.I32, pc);
Operand reg = context.AllocateLocal(OperandType.I32);
Operand isSysInst = context.ICompareEqual(context.BitwiseAnd(inst, Const(0xFFF80000)), Const(0xD5080000));
Operand lblSys = Label();
Operand lblEnd = Label();
context.BranchIfTrue(lblSys, isSysInst, BasicBlockFrequency.Cold);
context.Copy(reg, context.BitwiseAnd(context.ShiftRightUI(inst, Const(5)), Const(0x1F)));
context.Branch(lblEnd);
context.MarkLabel(lblSys);
context.Copy(reg, context.BitwiseAnd(inst, Const(0x1F)));
context.MarkLabel(lblEnd);
return reg;
}
public static bool SupportsFaultAddressPatchingForHost()
{
return SupportsFaultAddressPatchingForHostArch() && SupportsFaultAddressPatchingForHostOs();
}
private static bool SupportsFaultAddressPatchingForHostArch()
{
return RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
}
private static bool SupportsFaultAddressPatchingForHostOs()
{
return OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS();
}
}
}

View file

@ -16,7 +16,7 @@ namespace ARMeilleure.Signal
{
public delegate bool DebugPartialUnmap();
public delegate int DebugThreadLocalMapGetOrReserve(int threadId, int initialState);
public delegate void DebugNativeWriteLoop(IntPtr nativeWriteLoopPtr, IntPtr writePtr);
public delegate void DebugNativeWriteLoop(nint nativeWriteLoopPtr, nint writePtr);
public static DebugPartialUnmap GenerateDebugPartialUnmap()
{
@ -35,7 +35,7 @@ namespace ARMeilleure.Signal
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugPartialUnmap>();
}
public static DebugThreadLocalMapGetOrReserve GenerateDebugThreadLocalMapGetOrReserve(IntPtr structPtr)
public static DebugThreadLocalMapGetOrReserve GenerateDebugThreadLocalMapGetOrReserve(nint structPtr)
{
EmitterContext context = new();

View file

@ -2,7 +2,7 @@ using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Translation;
using Ryujinx.Common.Memory.PartialUnmaps;
using System;
using System.Runtime.InteropServices;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
namespace ARMeilleure.Signal
@ -10,17 +10,37 @@ namespace ARMeilleure.Signal
/// <summary>
/// Methods to handle signals caused by partial unmaps. See the structs for C# implementations of the methods.
/// </summary>
internal static class WindowsPartialUnmapHandler
internal static partial class WindowsPartialUnmapHandler
{
[LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "LoadLibraryA")]
private static partial nint LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName);
[LibraryImport("kernel32.dll", SetLastError = true)]
private static partial nint GetProcAddress(nint hModule, [MarshalAs(UnmanagedType.LPStr)] string procName);
private static nint _getCurrentThreadIdPtr;
public static nint GetCurrentThreadIdFunc()
{
if (_getCurrentThreadIdPtr == nint.Zero)
{
nint handle = LoadLibrary("kernel32.dll");
_getCurrentThreadIdPtr = GetProcAddress(handle, "GetCurrentThreadId");
}
return _getCurrentThreadIdPtr;
}
public static Operand EmitRetryFromAccessViolation(EmitterContext context)
{
IntPtr partialRemapStatePtr = PartialUnmapState.GlobalState;
IntPtr localCountsPtr = IntPtr.Add(partialRemapStatePtr, PartialUnmapState.LocalCountsOffset);
nint partialRemapStatePtr = PartialUnmapState.GlobalState;
nint localCountsPtr = nint.Add(partialRemapStatePtr, PartialUnmapState.LocalCountsOffset);
// Get the lock first.
EmitNativeReaderLockAcquire(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset));
EmitNativeReaderLockAcquire(context, nint.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset));
IntPtr getCurrentThreadId = WindowsSignalHandlerRegistration.GetCurrentThreadIdFunc();
nint getCurrentThreadId = GetCurrentThreadIdFunc();
Operand threadId = context.Call(Const((ulong)getCurrentThreadId), OperandType.I32);
Operand threadIndex = EmitThreadLocalMapIntGetOrReserve(context, localCountsPtr, threadId, Const(0));
@ -38,7 +58,7 @@ namespace ARMeilleure.Signal
Operand threadLocalPartialUnmapsPtr = EmitThreadLocalMapIntGetValuePtr(context, localCountsPtr, threadIndex);
Operand threadLocalPartialUnmaps = context.Load(OperandType.I32, threadLocalPartialUnmapsPtr);
Operand partialUnmapsCount = context.Load(OperandType.I32, Const((ulong)IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapsCountOffset)));
Operand partialUnmapsCount = context.Load(OperandType.I32, Const((ulong)nint.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapsCountOffset)));
context.Copy(retry, context.ICompareNotEqual(threadLocalPartialUnmaps, partialUnmapsCount));
@ -59,14 +79,14 @@ namespace ARMeilleure.Signal
context.MarkLabel(endLabel);
// Finally, release the lock and return the retry value.
EmitNativeReaderLockRelease(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset));
EmitNativeReaderLockRelease(context, nint.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset));
return retry;
}
public static Operand EmitThreadLocalMapIntGetOrReserve(EmitterContext context, IntPtr threadLocalMapPtr, Operand threadId, Operand initialState)
public static Operand EmitThreadLocalMapIntGetOrReserve(EmitterContext context, nint threadLocalMapPtr, Operand threadId, Operand initialState)
{
Operand idsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap<int>.ThreadIdsOffset));
Operand idsPtr = Const((ulong)nint.Add(threadLocalMapPtr, ThreadLocalMap<int>.ThreadIdsOffset));
Operand i = context.AllocateLocal(OperandType.I32);
@ -110,7 +130,7 @@ namespace ARMeilleure.Signal
// If it was 0, then we need to initialize the struct entry and return i.
context.BranchIfFalse(idNot0Label, context.ICompareEqual(existingId2, Const(0)));
Operand structsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap<int>.StructsOffset));
Operand structsPtr = Const((ulong)nint.Add(threadLocalMapPtr, ThreadLocalMap<int>.StructsOffset));
Operand structPtr = context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset2));
context.Store(structPtr, initialState);
@ -129,25 +149,14 @@ namespace ARMeilleure.Signal
return context.Copy(i);
}
private static Operand EmitThreadLocalMapIntGetValuePtr(EmitterContext context, IntPtr threadLocalMapPtr, Operand index)
private static Operand EmitThreadLocalMapIntGetValuePtr(EmitterContext context, nint threadLocalMapPtr, Operand index)
{
Operand offset = context.Multiply(index, Const(sizeof(int)));
Operand structsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap<int>.StructsOffset));
Operand structsPtr = Const((ulong)nint.Add(threadLocalMapPtr, ThreadLocalMap<int>.StructsOffset));
return context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset));
}
#pragma warning disable IDE0051 // Remove unused private member
private static void EmitThreadLocalMapIntRelease(EmitterContext context, IntPtr threadLocalMapPtr, Operand threadId, Operand index)
{
Operand offset = context.Multiply(index, Const(sizeof(int)));
Operand idsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap<int>.ThreadIdsOffset));
Operand idPtr = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset));
context.CompareAndSwap(idPtr, threadId, Const(0));
}
#pragma warning restore IDE0051
private static void EmitAtomicAddI32(EmitterContext context, Operand ptr, Operand additive)
{
Operand loop = Label();
@ -161,9 +170,9 @@ namespace ARMeilleure.Signal
context.BranchIfFalse(loop, context.ICompareEqual(initial, replaced));
}
private static void EmitNativeReaderLockAcquire(EmitterContext context, IntPtr nativeReaderLockPtr)
private static void EmitNativeReaderLockAcquire(EmitterContext context, nint nativeReaderLockPtr)
{
Operand writeLockPtr = Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.WriteLockOffset));
Operand writeLockPtr = Const((ulong)nint.Add(nativeReaderLockPtr, NativeReaderWriterLock.WriteLockOffset));
// Spin until we can acquire the write lock.
Operand spinLabel = Label();
@ -173,16 +182,16 @@ namespace ARMeilleure.Signal
context.BranchIfTrue(spinLabel, context.CompareAndSwap(writeLockPtr, Const(0), Const(1)));
// Increment reader count.
EmitAtomicAddI32(context, Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.ReaderCountOffset)), Const(1));
EmitAtomicAddI32(context, Const((ulong)nint.Add(nativeReaderLockPtr, NativeReaderWriterLock.ReaderCountOffset)), Const(1));
// Release write lock.
context.CompareAndSwap(writeLockPtr, Const(1), Const(0));
}
private static void EmitNativeReaderLockRelease(EmitterContext context, IntPtr nativeReaderLockPtr)
private static void EmitNativeReaderLockRelease(EmitterContext context, nint nativeReaderLockPtr)
{
// Decrement reader count.
EmitAtomicAddI32(context, Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.ReaderCountOffset)), Const(-1));
EmitAtomicAddI32(context, Const((ulong)nint.Add(nativeReaderLockPtr, NativeReaderWriterLock.ReaderCountOffset)), Const(-1));
}
}
}

View file

@ -1,44 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace ARMeilleure.Signal
{
unsafe partial class WindowsSignalHandlerRegistration
{
[LibraryImport("kernel32.dll")]
private static partial IntPtr AddVectoredExceptionHandler(uint first, IntPtr handler);
[LibraryImport("kernel32.dll")]
private static partial ulong RemoveVectoredExceptionHandler(IntPtr handle);
[LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "LoadLibraryA")]
private static partial IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName);
[LibraryImport("kernel32.dll", SetLastError = true)]
private static partial IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string procName);
private static IntPtr _getCurrentThreadIdPtr;
public static IntPtr RegisterExceptionHandler(IntPtr action)
{
return AddVectoredExceptionHandler(1, action);
}
public static bool RemoveExceptionHandler(IntPtr handle)
{
return RemoveVectoredExceptionHandler(handle) != 0;
}
public static IntPtr GetCurrentThreadIdFunc()
{
if (_getCurrentThreadIdPtr == IntPtr.Zero)
{
IntPtr handle = LoadLibrary("kernel32.dll");
_getCurrentThreadIdPtr = GetProcAddress(handle, "GetCurrentThreadId");
}
return _getCurrentThreadIdPtr;
}
}
}

View file

@ -9,7 +9,7 @@ namespace ARMeilleure.State
private readonly NativeContext _nativeContext;
internal IntPtr NativeContextPtr => _nativeContext.BasePtr;
internal nint NativeContextPtr => _nativeContext.BasePtr;
private bool _interrupted;

View file

@ -21,13 +21,14 @@ namespace ARMeilleure.State
public ulong ExclusiveValueLow;
public ulong ExclusiveValueHigh;
public int Running;
public long Tpidr2El0;
}
private static NativeCtxStorage _dummyStorage = new();
private readonly IJitMemoryBlock _block;
public IntPtr BasePtr => _block.Pointer;
public nint BasePtr => _block.Pointer;
public NativeContext(IJitMemoryAllocator allocator)
{
@ -176,6 +177,9 @@ namespace ARMeilleure.State
public long GetTpidrroEl0() => GetStorage().TpidrroEl0;
public void SetTpidrroEl0(long value) => GetStorage().TpidrroEl0 = value;
public long GetTpidr2El0() => GetStorage().Tpidr2El0;
public void SetTpidr2El0(long value) => GetStorage().Tpidr2El0 = value;
public int GetCounter() => GetStorage().Counter;
public void SetCounter(int value) => GetStorage().Counter = value;
@ -232,6 +236,11 @@ namespace ARMeilleure.State
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrroEl0);
}
public static int GetTpidr2El0Offset()
{
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Tpidr2El0);
}
public static int GetCounterOffset()
{
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter);

View file

@ -46,7 +46,7 @@ namespace ARMeilleure.Translation
public IMemoryManager Memory { get; }
public EntryTable<uint> CountTable { get; }
public AddressTable<ulong> FunctionTable { get; }
public IAddressTable<ulong> FunctionTable { get; }
public TranslatorStubs Stubs { get; }
public ulong EntryAddress { get; }
@ -62,7 +62,7 @@ namespace ARMeilleure.Translation
public ArmEmitterContext(
IMemoryManager memory,
EntryTable<uint> countTable,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
TranslatorStubs stubs,
ulong entryAddress,
bool highCq,
@ -92,7 +92,7 @@ namespace ARMeilleure.Translation
else
{
int index = Delegates.GetDelegateIndex(info);
IntPtr funcPtr = Delegates.GetDelegateFuncPtrByIndex(index);
nint funcPtr = Delegates.GetDelegateFuncPtrByIndex(index);
OperandType returnType = GetOperandType(info.ReturnType);

View file

@ -30,26 +30,21 @@ namespace ARMeilleure.Translation.Cache
_blocks.Add(new MemoryBlock(0, capacity));
}
public int Allocate(ref int size, int alignment)
public int Allocate(int size)
{
int alignM1 = alignment - 1;
for (int i = 0; i < _blocks.Count; i++)
{
MemoryBlock block = _blocks[i];
int misAlignment = ((block.Offset + alignM1) & (~alignM1)) - block.Offset;
int alignedSize = size + misAlignment;
if (block.Size > alignedSize)
if (block.Size > size)
{
size = alignedSize;
_blocks[i] = new MemoryBlock(block.Offset + alignedSize, block.Size - alignedSize);
return block.Offset + misAlignment;
_blocks[i] = new MemoryBlock(block.Offset + size, block.Size - size);
return block.Offset;
}
else if (block.Size == alignedSize)
else if (block.Size == size)
{
size = alignedSize;
_blocks.RemoveAt(i);
return block.Offset + misAlignment;
return block.Offset;
}
}

View file

@ -4,7 +4,6 @@ using ARMeilleure.Memory;
using ARMeilleure.Native;
using Ryujinx.Memory;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
@ -19,7 +18,6 @@ namespace ARMeilleure.Translation.Cache
private const int CodeAlignment = 4; // Bytes.
private const int CacheSize = 2047 * 1024 * 1024;
private const int CacheSizeIOS = 512 * 1024 * 1024;
private static ReservedRegion _jitRegion;
private static JitCacheInvalidation _jitCacheInvalidator;
@ -33,7 +31,7 @@ namespace ARMeilleure.Translation.Cache
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
public static partial IntPtr FlushInstructionCache(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize);
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
public static void Initialize(IJitMemoryAllocator allocator)
{
@ -49,9 +47,9 @@ namespace ARMeilleure.Translation.Cache
return;
}
_jitRegion = new ReservedRegion(allocator, (ulong)(OperatingSystem.IsIOS() ? CacheSizeIOS : CacheSize));
_jitRegion = new ReservedRegion(allocator, CacheSize);
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsIOS())
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
}
@ -67,17 +65,7 @@ namespace ARMeilleure.Translation.Cache
}
}
static ConcurrentQueue<(int funcOffset, int length)> _deferredRxProtect = new();
public static void RunDeferredRxProtects()
{
while (_deferredRxProtect.TryDequeue(out var result))
{
ReprotectAsExecutable(result.funcOffset, result.length);
}
}
public static IntPtr Map(CompiledFunction func, bool deferProtect)
public static nint Map(CompiledFunction func)
{
byte[] code = func.Code;
@ -85,31 +73,17 @@ namespace ARMeilleure.Translation.Cache
{
Debug.Assert(_initialized);
int funcOffset = Allocate(code.Length, deferProtect);
int funcOffset = Allocate(code.Length);
IntPtr funcPtr = _jitRegion.Pointer + funcOffset;
nint funcPtr = _jitRegion.Pointer + funcOffset;
if (OperatingSystem.IsIOS())
{
Marshal.Copy(code, 0, funcPtr, code.Length);
if (deferProtect)
{
_deferredRxProtect.Enqueue((funcOffset, code.Length));
}
else
{
ReprotectAsExecutable(funcOffset, code.Length);
JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
}
}
else if (OperatingSystem.IsMacOS()&& RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
unsafe
{
fixed (byte* codePtr = code)
{
JitSupportDarwin.Copy(funcPtr, (IntPtr)codePtr, (ulong)code.Length);
JitSupportDarwin.Copy(funcPtr, (nint)codePtr, (ulong)code.Length);
}
}
}
@ -121,7 +95,7 @@ namespace ARMeilleure.Translation.Cache
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (UIntPtr)code.Length);
FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (nuint)code.Length);
}
else
{
@ -135,13 +109,8 @@ namespace ARMeilleure.Translation.Cache
}
}
public static void Unmap(IntPtr pointer)
public static void Unmap(nint pointer)
{
if (OperatingSystem.IsIOS())
{
return;
}
lock (_lock)
{
Debug.Assert(_initialized);
@ -176,22 +145,11 @@ namespace ARMeilleure.Translation.Cache
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}
private static int Allocate(int codeSize, bool deferProtect = false)
private static int Allocate(int codeSize)
{
codeSize = AlignCodeSize(codeSize, deferProtect);
codeSize = AlignCodeSize(codeSize);
int alignment = CodeAlignment;
if (OperatingSystem.IsIOS() && !deferProtect)
{
alignment = 0x4000;
}
int allocOffset = _cacheAllocator.Allocate(ref codeSize, alignment);
//DEBUG: Show JIT Memory Allocation
//Console.WriteLine($"{allocOffset:x8}: {codeSize:x8} {alignment:x8}");
int allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset < 0)
{
@ -203,16 +161,9 @@ namespace ARMeilleure.Translation.Cache
return allocOffset;
}
private static int AlignCodeSize(int codeSize, bool deferProtect = false)
private static int AlignCodeSize(int codeSize)
{
int alignment = CodeAlignment;
if (OperatingSystem.IsIOS() && !deferProtect)
{
alignment = 0x4000;
}
return checked(codeSize + (alignment - 1)) & ~(alignment - 1);
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
}
private static void Add(int offset, int size, UnwindInfo unwindInfo)

View file

@ -68,7 +68,7 @@ namespace ARMeilleure.Translation.Cache
}
}
public void Invalidate(IntPtr basePointer, ulong size)
public void Invalidate(nint basePointer, ulong size)
{
if (_needsInvalidation)
{

View file

@ -40,7 +40,7 @@ namespace ARMeilleure.Translation.Cache
PushMachframe = 10,
}
private unsafe delegate RuntimeFunction* GetRuntimeFunctionCallback(ulong controlPc, IntPtr context);
private unsafe delegate RuntimeFunction* GetRuntimeFunctionCallback(ulong controlPc, nint context);
[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
@ -49,7 +49,7 @@ namespace ARMeilleure.Translation.Cache
ulong baseAddress,
uint length,
GetRuntimeFunctionCallback callback,
IntPtr context,
nint context,
[MarshalAs(UnmanagedType.LPWStr)] string outOfProcessCallbackDll);
private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback;
@ -60,7 +60,7 @@ namespace ARMeilleure.Translation.Cache
private unsafe static UnwindInfo* _unwindInfo;
public static void InstallFunctionTableHandler(IntPtr codeCachePointer, uint codeCacheLength, IntPtr workBufferPtr)
public static void InstallFunctionTableHandler(nint codeCachePointer, uint codeCacheLength, nint workBufferPtr)
{
ulong codeCachePtr = (ulong)codeCachePointer.ToInt64();
@ -91,7 +91,7 @@ namespace ARMeilleure.Translation.Cache
}
}
private static unsafe RuntimeFunction* FunctionTableHandler(ulong controlPc, IntPtr context)
private static unsafe RuntimeFunction* FunctionTableHandler(ulong controlPc, nint context)
{
int offset = (int)((long)controlPc - context.ToInt64());
@ -114,7 +114,7 @@ namespace ARMeilleure.Translation.Cache
{
int stackOffset = entry.StackOffsetOrAllocSize;
// Debug.Assert(stackOffset % 16 == 0);
Debug.Assert(stackOffset % 16 == 0);
if (stackOffset <= 0xFFFF0)
{
@ -135,7 +135,7 @@ namespace ARMeilleure.Translation.Cache
{
int allocSize = entry.StackOffsetOrAllocSize;
// Debug.Assert(allocSize % 8 == 0);
Debug.Assert(allocSize % 8 == 0);
if (allocSize <= 128)
{

View file

@ -11,7 +11,7 @@ namespace ARMeilleure.Translation
private int[] _postOrderMap;
public int LocalsCount { get; private set; }
public BasicBlock Entry { get; }
public BasicBlock Entry { get; private set; }
public IntrusiveList<BasicBlock> Blocks { get; }
public BasicBlock[] PostOrderBlocks => _postOrderBlocks;
public int[] PostOrderMap => _postOrderMap;
@ -34,6 +34,15 @@ namespace ARMeilleure.Translation
return result;
}
public void UpdateEntry(BasicBlock newEntry)
{
newEntry.AddSuccessor(Entry);
Entry = newEntry;
Blocks.AddFirst(newEntry);
Update();
}
public void Update()
{
RemoveUnreachableBlocks(Blocks);

View file

@ -8,9 +8,9 @@ namespace ARMeilleure.Translation
private readonly Delegate _dlg; // Ensure that this delegate will not be garbage collected.
#pragma warning restore IDE0052
public IntPtr FuncPtr { get; }
public nint FuncPtr { get; }
public DelegateInfo(Delegate dlg, IntPtr funcPtr)
public DelegateInfo(Delegate dlg, nint funcPtr)
{
_dlg = dlg;
FuncPtr = funcPtr;

View file

@ -9,7 +9,7 @@ namespace ARMeilleure.Translation
{
static class Delegates
{
public static bool TryGetDelegateFuncPtrByIndex(int index, out IntPtr funcPtr)
public static bool TryGetDelegateFuncPtrByIndex(int index, out nint funcPtr)
{
if (index >= 0 && index < _delegates.Count)
{
@ -25,7 +25,7 @@ namespace ARMeilleure.Translation
}
}
public static IntPtr GetDelegateFuncPtrByIndex(int index)
public static nint GetDelegateFuncPtrByIndex(int index)
{
if (index < 0 || index >= _delegates.Count)
{
@ -35,7 +35,7 @@ namespace ARMeilleure.Translation
return _delegates.Values[index].FuncPtr; // O(1).
}
public static IntPtr GetDelegateFuncPtr(MethodInfo info)
public static nint GetDelegateFuncPtr(MethodInfo info)
{
ArgumentNullException.ThrowIfNull(info);
@ -65,7 +65,7 @@ namespace ARMeilleure.Translation
return index;
}
private static void SetDelegateInfo(Delegate dlg, IntPtr funcPtr)
private static void SetDelegateInfo(Delegate dlg, nint funcPtr)
{
string key = GetKey(dlg.Method);

View file

@ -2,6 +2,6 @@ using System;
namespace ARMeilleure.Translation
{
delegate void DispatcherFunction(IntPtr nativeContext, ulong startAddress);
delegate ulong WrapperFunction(IntPtr nativeContext, ulong startAddress);
delegate void DispatcherFunction(nint nativeContext, ulong startAddress);
delegate ulong WrapperFunction(nint nativeContext, ulong startAddress);
}

View file

@ -77,7 +77,7 @@ namespace ARMeilleure.Translation
{
continue;
}
for (int pBlkIndex = 0; pBlkIndex < block.Predecessors.Count; pBlkIndex++)
{
BasicBlock current = block.Predecessors[pBlkIndex];

View file

@ -97,7 +97,7 @@ namespace ARMeilleure.Translation
public virtual Operand Call(MethodInfo info, params Operand[] callArgs)
{
IntPtr funcPtr = Delegates.GetDelegateFuncPtr(info);
nint funcPtr = Delegates.GetDelegateFuncPtr(info);
OperandType returnType = GetOperandType(info.ReturnType);

View file

@ -2,5 +2,5 @@ using System;
namespace ARMeilleure.Translation
{
delegate ulong GuestFunction(IntPtr nativeContextPtr);
delegate ulong GuestFunction(nint nativeContextPtr);
}

View file

@ -3,7 +3,6 @@ using ARMeilleure.CodeGen.Linking;
using ARMeilleure.CodeGen.Unwinding;
using ARMeilleure.Common;
using ARMeilleure.Memory;
using ARMeilleure.Translation.Cache;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
@ -14,6 +13,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -30,7 +30,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 5518; //! To be incremented manually for each change to the ARMeilleure project.
private const uint InternalVersion = 6992; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";
@ -41,6 +41,7 @@ namespace ARMeilleure.Translation.PTC
public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1);
public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2);
public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3);
public static readonly Symbol FunctionTableSymbol = new(SymbolType.Special, 4);
private const byte FillingByte = 0x00;
private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
@ -101,7 +102,7 @@ namespace ARMeilleure.Translation.PTC
Disable();
}
public void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerType memoryMode)
public void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerType memoryMode, string cacheSelector)
{
Wait();
@ -127,6 +128,8 @@ namespace ARMeilleure.Translation.PTC
DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
_memoryMode = memoryMode;
Logger.Info?.Print(LogClass.Ptc, $"PPTC (v{InternalVersion}) Profile: {DisplayVersion}-{cacheSelector}");
string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir);
string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir);
@ -140,8 +143,8 @@ namespace ARMeilleure.Translation.PTC
Directory.CreateDirectory(workPathBackup);
}
CachePathActual = Path.Combine(workPathActual, DisplayVersion);
CachePathBackup = Path.Combine(workPathBackup, DisplayVersion);
CachePathActual = Path.Combine(workPathActual, DisplayVersion) + "-" + cacheSelector;
CachePathBackup = Path.Combine(workPathBackup, DisplayVersion) + "-" + cacheSelector;
PreLoad();
Profiler.PreLoad();
@ -269,11 +272,11 @@ namespace ARMeilleure.Translation.PTC
return false;
}
IntPtr intPtr = IntPtr.Zero;
nint intPtr = nint.Zero;
try
{
intPtr = Marshal.AllocHGlobal(new IntPtr(outerHeader.UncompressedStreamSize));
intPtr = Marshal.AllocHGlobal(new nint(outerHeader.UncompressedStreamSize));
using UnmanagedMemoryStream stream = new((byte*)intPtr.ToPointer(), outerHeader.UncompressedStreamSize, outerHeader.UncompressedStreamSize, FileAccess.ReadWrite);
try
@ -310,7 +313,7 @@ namespace ARMeilleure.Translation.PTC
ReadOnlySpan<byte> infosBytes = new(stream.PositionPointer, innerHeader.InfosLength);
stream.Seek(innerHeader.InfosLength, SeekOrigin.Current);
Hash128 infosHash = XXHash128.ComputeHash(infosBytes);
Hash128 infosHash = Hash128.ComputeHash(infosBytes);
if (innerHeader.InfosHash != infosHash)
{
@ -322,7 +325,7 @@ namespace ARMeilleure.Translation.PTC
ReadOnlySpan<byte> codesBytes = (int)innerHeader.CodesLength > 0 ? new(stream.PositionPointer, (int)innerHeader.CodesLength) : ReadOnlySpan<byte>.Empty;
stream.Seek(innerHeader.CodesLength, SeekOrigin.Current);
Hash128 codesHash = XXHash128.ComputeHash(codesBytes);
Hash128 codesHash = Hash128.ComputeHash(codesBytes);
if (innerHeader.CodesHash != codesHash)
{
@ -334,7 +337,7 @@ namespace ARMeilleure.Translation.PTC
ReadOnlySpan<byte> relocsBytes = new(stream.PositionPointer, innerHeader.RelocsLength);
stream.Seek(innerHeader.RelocsLength, SeekOrigin.Current);
Hash128 relocsHash = XXHash128.ComputeHash(relocsBytes);
Hash128 relocsHash = Hash128.ComputeHash(relocsBytes);
if (innerHeader.RelocsHash != relocsHash)
{
@ -346,7 +349,7 @@ namespace ARMeilleure.Translation.PTC
ReadOnlySpan<byte> unwindInfosBytes = new(stream.PositionPointer, innerHeader.UnwindInfosLength);
stream.Seek(innerHeader.UnwindInfosLength, SeekOrigin.Current);
Hash128 unwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes);
Hash128 unwindInfosHash = Hash128.ComputeHash(unwindInfosBytes);
if (innerHeader.UnwindInfosHash != unwindInfosHash)
{
@ -374,7 +377,7 @@ namespace ARMeilleure.Translation.PTC
}
finally
{
if (intPtr != IntPtr.Zero)
if (intPtr != nint.Zero)
{
Marshal.FreeHGlobal(intPtr);
}
@ -456,11 +459,11 @@ namespace ARMeilleure.Translation.PTC
outerHeader.SetHeaderHash();
IntPtr intPtr = IntPtr.Zero;
nint intPtr = nint.Zero;
try
{
intPtr = Marshal.AllocHGlobal(new IntPtr(outerHeader.UncompressedStreamSize));
intPtr = Marshal.AllocHGlobal(new nint(outerHeader.UncompressedStreamSize));
using UnmanagedMemoryStream stream = new((byte*)intPtr.ToPointer(), outerHeader.UncompressedStreamSize, outerHeader.UncompressedStreamSize, FileAccess.ReadWrite);
stream.Seek((long)Unsafe.SizeOf<InnerHeader>(), SeekOrigin.Begin);
@ -479,10 +482,10 @@ namespace ARMeilleure.Translation.PTC
Debug.Assert(stream.Position == stream.Length);
innerHeader.InfosHash = XXHash128.ComputeHash(infosBytes);
innerHeader.CodesHash = XXHash128.ComputeHash(codesBytes);
innerHeader.RelocsHash = XXHash128.ComputeHash(relocsBytes);
innerHeader.UnwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes);
innerHeader.InfosHash = Hash128.ComputeHash(infosBytes);
innerHeader.CodesHash = Hash128.ComputeHash(codesBytes);
innerHeader.RelocsHash = Hash128.ComputeHash(relocsBytes);
innerHeader.UnwindInfosHash = Hash128.ComputeHash(unwindInfosBytes);
innerHeader.SetHeaderHash();
@ -514,7 +517,7 @@ namespace ARMeilleure.Translation.PTC
}
finally
{
if (intPtr != IntPtr.Zero)
if (intPtr != nint.Zero)
{
Marshal.FreeHGlobal(intPtr);
}
@ -665,7 +668,7 @@ namespace ARMeilleure.Translation.PTC
foreach (RelocEntry relocEntry in relocEntries)
{
IntPtr? imm = null;
nint? imm = null;
Symbol symbol = relocEntry.Symbol;
if (symbol.Type == SymbolType.FunctionTable)
@ -676,7 +679,7 @@ namespace ARMeilleure.Translation.PTC
{
unsafe
{
imm = (IntPtr)Unsafe.AsPointer(ref translator.FunctionTable.GetValue(guestAddress));
imm = (nint)Unsafe.AsPointer(ref translator.FunctionTable.GetValue(guestAddress));
}
}
}
@ -684,7 +687,7 @@ namespace ARMeilleure.Translation.PTC
{
int index = (int)symbol.Value;
if (Delegates.TryGetDelegateFuncPtrByIndex(index, out IntPtr funcPtr))
if (Delegates.TryGetDelegateFuncPtrByIndex(index, out nint funcPtr))
{
imm = funcPtr;
}
@ -699,13 +702,17 @@ namespace ARMeilleure.Translation.PTC
unsafe
{
imm = (IntPtr)Unsafe.AsPointer(ref callCounter.Value);
imm = (nint)Unsafe.AsPointer(ref callCounter.Value);
}
}
else if (symbol == DispatchStubSymbol)
{
imm = translator.Stubs.DispatchStub;
}
else if (symbol == FunctionTableSymbol)
{
imm = translator.FunctionTable.Base;
}
if (imm == null)
{
@ -745,7 +752,7 @@ namespace ARMeilleure.Translation.PTC
bool highCq)
{
var cFunc = new CompiledFunction(code, unwindInfo, RelocInfo.Empty);
var gFunc = cFunc.MapWithPointer<GuestFunction>(out IntPtr gFuncPointer, true);
var gFunc = cFunc.MapWithPointer<GuestFunction>(out nint gFuncPointer);
return new TranslatedFunction(gFunc, gFuncPointer, callCounter, guestSize, highCq);
}
@ -796,10 +803,15 @@ namespace ARMeilleure.Translation.PTC
return;
}
int degreeOfParallelism = Environment.ProcessorCount;
if (Optimizations.LowPower)
degreeOfParallelism /= 3;
// If there are enough cores lying around, we leave one alone for other tasks.
if (degreeOfParallelism > 4)
if (degreeOfParallelism > 4 && !Optimizations.LowPower)
{
degreeOfParallelism--;
}
@ -827,7 +839,7 @@ namespace ARMeilleure.Translation.PTC
Debug.Assert(Profiler.IsAddressInStaticCodeRange(address));
TranslatedFunction func = translator.Translate(address, item.funcProfile.Mode, item.funcProfile.HighCq, deferProtect: true);
TranslatedFunction func = translator.Translate(address, item.funcProfile.Mode, item.funcProfile.HighCq);
bool isAddressUnique = translator.Functions.TryAdd(address, func.GuestSize, func);
@ -844,22 +856,26 @@ namespace ARMeilleure.Translation.PTC
}
}
List<Thread> threads = new();
for (int i = 0; i < degreeOfParallelism; i++)
{
Thread thread = new(TranslateFuncs)
{
IsBackground = true,
};
threads.Add(thread);
}
List<Thread> threads = Enumerable.Range(0, degreeOfParallelism)
.Select(idx =>
new Thread(TranslateFuncs)
{
IsBackground = true,
Name = "Ptc.TranslateThread." + idx
}
).ToList();
Stopwatch sw = Stopwatch.StartNew();
threads.ForEach((thread) => thread.Start());
threads.ForEach((thread) => thread.Join());
foreach (var thread in threads)
{
thread.Start();
}
foreach (var thread in threads)
{
thread.Join();
}
threads.Clear();
@ -875,6 +891,7 @@ namespace ARMeilleure.Translation.PTC
Thread preSaveThread = new(PreSave)
{
IsBackground = true,
Name = "Ptc.DiskWriter"
};
preSaveThread.Start();
}
@ -902,7 +919,7 @@ namespace ARMeilleure.Translation.PTC
public static Hash128 ComputeHash(IMemoryManager memory, ulong address, ulong guestSize)
{
return XXHash128.ComputeHash(memory.GetSpan(address, checked((int)(guestSize))));
return Hash128.ComputeHash(memory.GetSpan(address, checked((int)(guestSize))));
}
public void WriteCompiledFunction(ulong address, ulong guestSize, Hash128 hash, bool highCq, CompiledFunction compiledFunc)
@ -1005,7 +1022,6 @@ namespace ARMeilleure.Translation.PTC
osPlatform |= (OperatingSystem.IsLinux() ? 1u : 0u) << 1;
osPlatform |= (OperatingSystem.IsMacOS() ? 1u : 0u) << 2;
osPlatform |= (OperatingSystem.IsWindows() ? 1u : 0u) << 3;
osPlatform |= (OperatingSystem.IsIOS() ? 1u : 0u) << 4;
#pragma warning restore IDE0055
return osPlatform;
@ -1032,14 +1048,14 @@ namespace ARMeilleure.Translation.PTC
{
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]);
HeaderHash = Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]);
}
public bool IsHeaderValid()
{
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
return Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
}
}
@ -1067,14 +1083,14 @@ namespace ARMeilleure.Translation.PTC
{
Span<InnerHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>())]);
HeaderHash = Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>())]);
}
public bool IsHeaderValid()
{
Span<InnerHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
return Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
}
}

View file

@ -209,7 +209,7 @@ namespace ARMeilleure.Translation.PTC
Hash128 expectedHash = DeserializeStructure<Hash128>(stream);
Hash128 actualHash = XXHash128.ComputeHash(GetReadOnlySpan(stream));
Hash128 actualHash = Hash128.ComputeHash(GetReadOnlySpan(stream));
if (actualHash != expectedHash)
{
@ -313,7 +313,7 @@ namespace ARMeilleure.Translation.PTC
Debug.Assert(stream.Position == stream.Length);
stream.Seek(Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
Hash128 hash = XXHash128.ComputeHash(GetReadOnlySpan(stream));
Hash128 hash = Hash128.ComputeHash(GetReadOnlySpan(stream));
stream.Seek(0L, SeekOrigin.Begin);
SerializeStructure(stream, hash);
@ -374,14 +374,14 @@ namespace ARMeilleure.Translation.PTC
{
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]);
HeaderHash = Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]);
}
public bool IsHeaderValid()
{
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
return Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
}
}

View file

@ -89,6 +89,17 @@ namespace ARMeilleure.Translation
public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode)
{
if (cfg.Entry.Predecessors.Count != 0)
{
// We expect the entry block to have no predecessors.
// This is required because we have a implicit context load at the start of the function,
// but if there is a jump to the start of the function, the context load would trash the modified values.
// Here we insert a new entry block that will jump to the existing entry block.
BasicBlock newEntry = new BasicBlock(cfg.Blocks.Count);
cfg.UpdateEntry(newEntry);
}
// Compute local register inputs and outputs used inside blocks.
RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count];
RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count];
@ -201,7 +212,7 @@ namespace ARMeilleure.Translation
// The only block without any predecessor should be the entry block.
// It always needs a context load as it is the first block to run.
if (block.Predecessors.Count == 0 || hasContextLoad)
if (block == cfg.Entry || hasContextLoad)
{
long vecMask = globalInputs[block.Index].VecMask;
long intMask = globalInputs[block.Index].IntMask;

View file

@ -7,12 +7,12 @@ namespace ARMeilleure.Translation
{
private readonly GuestFunction _func; // Ensure that this delegate will not be garbage collected.
public IntPtr FuncPointer { get; }
public nint FuncPointer { get; }
public Counter<uint> CallCounter { get; }
public ulong GuestSize { get; }
public bool HighCq { get; }
public TranslatedFunction(GuestFunction func, IntPtr funcPointer, Counter<uint> callCounter, ulong guestSize, bool highCq)
public TranslatedFunction(GuestFunction func, nint funcPointer, Counter<uint> callCounter, ulong guestSize, bool highCq)
{
_func = func;
FuncPointer = funcPointer;

View file

@ -22,33 +22,13 @@ namespace ARMeilleure.Translation
{
public class Translator
{
private static readonly AddressTable<ulong>.Level[] _levels64Bit =
new AddressTable<ulong>.Level[]
{
new(31, 17),
new(23, 8),
new(15, 8),
new( 7, 8),
new( 2, 5),
};
private static readonly AddressTable<ulong>.Level[] _levels32Bit =
new AddressTable<ulong>.Level[]
{
new(31, 17),
new(23, 8),
new(15, 8),
new( 7, 8),
new( 1, 6),
};
private readonly IJitMemoryAllocator _allocator;
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
private readonly Ptc _ptc;
internal TranslatorCache<TranslatedFunction> Functions { get; }
internal AddressTable<ulong> FunctionTable { get; }
internal IAddressTable<ulong> FunctionTable { get; }
internal EntryTable<uint> CountTable { get; }
internal TranslatorStubs Stubs { get; }
internal TranslatorQueue Queue { get; }
@ -57,10 +37,7 @@ namespace ARMeilleure.Translation
private Thread[] _backgroundTranslationThreads;
private volatile int _threadCount;
// FIXME: Remove this once the init logic of the emulator will be redone.
public static readonly ManualResetEvent IsReadyForTranslation = new(false);
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits)
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, IAddressTable<ulong> functionTable)
{
_allocator = allocator;
Memory = memory;
@ -75,20 +52,15 @@ namespace ARMeilleure.Translation
CountTable = new EntryTable<uint>();
Functions = new TranslatorCache<TranslatedFunction>();
FunctionTable = new AddressTable<ulong>(for64Bits ? _levels64Bit : _levels32Bit);
FunctionTable = functionTable;
Stubs = new TranslatorStubs(FunctionTable);
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
if (memory.Type.IsHostMappedOrTracked())
{
NativeSignalHandler.InitializeSignalHandler(allocator.GetPageSize());
}
}
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
{
_ptc.Initialize(titleIdText, displayVersion, enabled, Memory.Type);
_ptc.Initialize(titleIdText, displayVersion, enabled, Memory.Type, cacheSelector);
return _ptc;
}
@ -105,15 +77,11 @@ namespace ARMeilleure.Translation
{
if (Interlocked.Increment(ref _threadCount) == 1)
{
IsReadyForTranslation.WaitOne();
if (_ptc.State == PtcState.Enabled)
{
Debug.Assert(Functions.Count == 0);
_ptc.LoadTranslations(this);
_ptc.MakeAndSaveTranslations(this);
JitCache.RunDeferredRxProtects();
}
_ptc.Profiler.Start();
@ -252,7 +220,7 @@ namespace ARMeilleure.Translation
}
}
internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false, bool deferProtect = false)
internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false)
{
var context = new ArmEmitterContext(
Memory,
@ -310,7 +278,7 @@ namespace ARMeilleure.Translation
_ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc);
}
GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(out IntPtr funcPointer, deferProtect);
GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(out nint funcPointer);
Allocators.ResetAll();

View file

@ -80,7 +80,10 @@ namespace ARMeilleure.Translation
return true;
}
Monitor.Wait(Sync);
if (!_disposed)
{
Monitor.Wait(Sync);
}
}
}

View file

@ -15,12 +15,12 @@ namespace ARMeilleure.Translation
/// </summary>
class TranslatorStubs : IDisposable
{
private readonly Lazy<IntPtr> _slowDispatchStub;
private readonly Lazy<nint> _slowDispatchStub;
private bool _disposed;
private readonly AddressTable<ulong> _functionTable;
private readonly Lazy<IntPtr> _dispatchStub;
private readonly IAddressTable<ulong> _functionTable;
private readonly Lazy<nint> _dispatchStub;
private readonly Lazy<DispatcherFunction> _dispatchLoop;
private readonly Lazy<WrapperFunction> _contextWrapper;
@ -28,7 +28,7 @@ namespace ARMeilleure.Translation
/// Gets the dispatch stub.
/// </summary>
/// <exception cref="ObjectDisposedException"><see cref="TranslatorStubs"/> instance was disposed</exception>
public IntPtr DispatchStub
public nint DispatchStub
{
get
{
@ -42,7 +42,7 @@ namespace ARMeilleure.Translation
/// Gets the slow dispatch stub.
/// </summary>
/// <exception cref="ObjectDisposedException"><see cref="TranslatorStubs"/> instance was disposed</exception>
public IntPtr SlowDispatchStub
public nint SlowDispatchStub
{
get
{
@ -86,7 +86,7 @@ namespace ARMeilleure.Translation
/// </summary>
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
public TranslatorStubs(AddressTable<ulong> functionTable)
public TranslatorStubs(IAddressTable<ulong> functionTable)
{
ArgumentNullException.ThrowIfNull(functionTable);
@ -140,7 +140,7 @@ namespace ARMeilleure.Translation
/// Generates a <see cref="DispatchStub"/>.
/// </summary>
/// <returns>Generated <see cref="DispatchStub"/></returns>
private IntPtr GenerateDispatchStub()
private nint GenerateDispatchStub()
{
var context = new EmitterContext();
@ -198,7 +198,7 @@ namespace ARMeilleure.Translation
/// Generates a <see cref="SlowDispatchStub"/>.
/// </summary>
/// <returns>Generated <see cref="SlowDispatchStub"/></returns>
private IntPtr GenerateSlowDispatchStub()
private nint GenerateSlowDispatchStub()
{
var context = new EmitterContext();

View file

@ -9,7 +9,7 @@ namespace ARMeilleure.Translation
{
public static class TranslatorTestMethods
{
public delegate int FpFlagsPInvokeTest(IntPtr managedMethod);
public delegate int FpFlagsPInvokeTest(nint managedMethod);
private static bool SetPlatformFtz(EmitterContext context, bool ftz)
{

View file

@ -7,7 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
4E551F202CF128540096A2DF /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
25BFA0892CF956FD0085F3E4 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
4E80AA212CD705DD00029585 /* SDL in Frameworks */ = {isa = PBXBuildFile; productRef = 4E80AA202CD705DD00029585 /* SDL */; };
/* End PBXBuildFile section */
@ -59,7 +59,7 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
4E9A82F32CF87822006D7086 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */ = {
4E80AA0A2CD6FAA800029585 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */ = {
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
attributesByRelativePath = {
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (CodeSignOnCopy, );
@ -68,6 +68,7 @@
"Dependencies/Dynamic Libraries/libavutil.dylib" = (CodeSignOnCopy, );
Dependencies/XCFrameworks/MoltenVK.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/SDL2.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libSPIRV.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libavcodec.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libavfilter.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libavformat.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
@ -86,6 +87,7 @@
Dependencies/XCFrameworks/libavfilter.xcframework,
Dependencies/XCFrameworks/libavformat.xcframework,
Dependencies/XCFrameworks/libavutil.xcframework,
Dependencies/XCFrameworks/libSPIRV.xcframework,
Dependencies/XCFrameworks/libswresample.xcframework,
Dependencies/XCFrameworks/libswscale.xcframework,
Dependencies/XCFrameworks/libteakra.xcframework,
@ -100,7 +102,7 @@
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
4E80AA1D2CD7015100029585 /* Exceptions for "MeloNX" folder in "MeloNX" target */,
4E9A82F32CF87822006D7086 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */,
4E80AA0A2CD6FAA800029585 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */,
);
path = MeloNX;
sourceTree = "<group>";
@ -122,8 +124,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4E551F202CF128540096A2DF /* GameController.framework in Frameworks */,
4E80AA212CD705DD00029585 /* SDL in Frameworks */,
25BFA0892CF956FD0085F3E4 /* GameController.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -483,7 +485,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 95J8WZ4TN8;
DEVELOPMENT_TEAM = 4TD3JXVDW7;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = MeloNX/Info.plist;
@ -553,10 +555,9 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_BUNDLE_IDENTIFIER = com.benlawrence.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/Core/Headers/Ryujinx-Header.h";
@ -574,7 +575,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 95J8WZ4TN8;
DEVELOPMENT_TEAM = 4TD3JXVDW7;
ENABLE_PREVIEWS = YES;
GCC_OPTIMIZATION_LEVEL = 3;
GENERATE_INFOPLIST_FILE = YES;
@ -645,10 +646,9 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_BUNDLE_IDENTIFIER = com.benlawrence.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/Core/Headers/Ryujinx-Header.h";

View file

@ -49,7 +49,7 @@ class Ryujinx {
var listinputids: Bool
var fullscreen: Bool
var memoryManagerMode: String
var disableVSync: Bool
var vsync: String
var disableShaderCache: Bool
var disableDockedMode: Bool
var enableTextureRecompression: Bool
@ -60,14 +60,14 @@ class Ryujinx {
inputids: [String] = [],
debuglogs: Bool = false,
tracelogs: Bool = false,
listinputids: Bool = false,
fullscreen: Bool = true,
memoryManagerMode: String = "HostMapped",
disableVSync: Bool = false,
disableShaderCache: Bool = false,
disableDockedMode: Bool = false,
nintendoinput: Bool = true,
enableInternet: Bool = false,
listinputids: Bool = false,
fullscreen: Bool = true,
memoryManagerMode: String = "HostMappedUnsafe",
vsync: String = "Switch",
disableShaderCache: Bool = false,
disableDockedMode: Bool = false,
enableTextureRecompression: Bool = true,
additionalArgs: [String] = [],
resscale: Float = 1.00
@ -76,17 +76,17 @@ class Ryujinx {
self.inputids = inputids
self.debuglogs = debuglogs
self.tracelogs = tracelogs
self.nintendoinput = nintendoinput
self.enableInternet = enableInternet
self.listinputids = listinputids
self.fullscreen = fullscreen
self.disableVSync = disableVSync
self.vsync = vsync
self.disableShaderCache = disableShaderCache
self.disableDockedMode = disableDockedMode
self.enableTextureRecompression = enableTextureRecompression
self.additionalArgs = additionalArgs
self.memoryManagerMode = memoryManagerMode
self.resscale = resscale
self.nintendoinput = nintendoinput
self.enableInternet = enableInternet
}
}
@ -157,17 +157,15 @@ class Ryujinx {
args.append(contentsOf: ["--resolution-scale", String(config.resscale)])
}
// Adding default args directly into additionalArgs
if config.nintendoinput {
args.append("--correct-ons-controller")
}
if config.enableInternet {
args.append("--enable-internet-connection")
}
// Adding default args directly into additionalArgs
if config.disableVSync {
args.append("--disable-vsync")
}
if config.disableShaderCache {
args.append("--disable-shader-cache")
}

View file

@ -12,21 +12,29 @@ struct SettingsView: View {
@Binding var MoltenVKSettings: [MoltenVKSettings]
var memoryManagerModes = [
("HostMappedUnsafe", "Host Unchecked (fastest, unstable / unsafe)"),
("HostMapped", "Host (fast)"),
("HostMappedUnsafe", "Host Unchecked (fast, unstable / unsafe)"),
("SoftwarePageTable", "Software (slow)"),
("SoftwarePageTable", "Software")
]
var vsyncModes = [
("Switch", "Switch"),
("Unbound", "Unbound"),
]
@AppStorage("RyuDemoControls") var ryuDemo: Bool = false
@AppStorage("MTL_HUD_ENABLED") var metalHUDEnabled: Bool = false
var body: some View {
ScrollView {
VStack {
Section(header: Title("Graphics and Performance")) {
Section(header: Text("Graphics and Performance").bold()) {
Toggle("Ryujinx Fullscreen", isOn: $config.fullscreen)
Toggle("Disable V-Sync", isOn: $config.disableVSync)
Picker("V-Sync", selection: $config.vsync) {
ForEach(vsyncModes, id: \.0) { key, displayName in
Text(displayName).tag(key)
}
}
Toggle("Disable Shader Cache", isOn: $config.disableShaderCache)
Toggle("Enable Texture Recompression", isOn: $config.enableTextureRecompression)
Toggle("Disable Docked Mode", isOn: $config.disableDockedMode)
@ -41,18 +49,19 @@ struct SettingsView: View {
}
}
Section(header: Title("Input Settings")) {
Section(header: Text("Input Settings").bold()) {
Toggle("List Input IDs", isOn: $config.listinputids)
Toggle("Nintendo Controller Layout", isOn: $config.nintendoinput)
Toggle("Ryujinx Demo On-Screen Controller", isOn: $ryuDemo)
// Toggle("Host Mapped Memory", isOn: $config.hostMappedMemory)
Toggle("Disable Docked Mode", isOn: $config.disableDockedMode)
}
Section(header: Title("Logging Settings")) {
Section(header: Text("Logging Settings").bold()) {
Toggle("Enable Debug Logs", isOn: $config.debuglogs)
Toggle("Enable Trace Logs", isOn: $config.tracelogs)
}
Section(header: Title("CPU Mode")) {
Section(header: Text("CPU Mode").bold()) {
HStack {
Spacer()
Picker("Memory Manager Mode", selection: $config.memoryManagerMode) {
@ -66,7 +75,7 @@ struct SettingsView: View {
Section(header: Title("Additional Settings")) {
Section(header: Text("Additional Settings")) {
//TextField("Game Path", text: $config.gamepath)
Text("PageSize \(String(Int(getpagesize())))")
@ -81,8 +90,8 @@ struct SettingsView: View {
))
}
}
.padding()
}
.padding()
.onAppear {
if let configs = loadSettings() {
self.config = configs
@ -164,20 +173,3 @@ extension NumberFormatter {
return formatter
}
}
struct Title: View {
let string: String
init(_ string: String) {
self.string = string
}
var body: some View {
VStack {
Text(string)
.font(.title2)
Divider()
}
}
}

View file

@ -20,9 +20,28 @@ namespace Ryujinx.Audio.Backends.OpenAL
private bool _stillRunning;
private readonly Thread _updaterThread;
private float _volume;
public float Volume
{
get
{
return _volume;
}
set
{
_volume = value;
foreach (OpenALHardwareDeviceSession session in _sessions.Keys)
{
session.UpdateMasterVolume(value);
}
}
}
public OpenALHardwareDeviceDriver()
{
_device = ALC.OpenDevice("");
_device = ALC.OpenDevice(string.Empty);
_context = ALC.CreateContext(_device, new ALContextAttributes());
_updateRequiredEvent = new ManualResetEvent(false);
_pauseEvent = new ManualResetEvent(true);
@ -34,6 +53,8 @@ namespace Ryujinx.Audio.Backends.OpenAL
Name = "HardwareDeviceDriver.OpenAL",
};
_volume = 1f;
_updaterThread.Start();
}
@ -52,7 +73,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
}
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
{
@ -73,7 +94,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
throw new ArgumentException($"{channelCount}");
}
OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0);

View file

@ -16,10 +16,11 @@ namespace Ryujinx.Audio.Backends.OpenAL
private bool _isActive;
private readonly Queue<OpenALAudioBuffer> _queuedBuffers;
private ulong _playedSampleCount;
private float _volume;
private readonly object _lock = new();
public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_driver = driver;
_queuedBuffers = new Queue<OpenALAudioBuffer>();
@ -27,7 +28,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
_targetFormat = GetALFormat();
_isActive = false;
_playedSampleCount = 0;
SetVolume(requestedVolume);
SetVolume(1f);
}
private ALFormat GetALFormat()
@ -65,7 +66,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
{
OpenALAudioBuffer driverBuffer = new()
{
DriverIdentifier = buffer.HostTag,
DriverIdentifier = buffer.DataPointer,
BufferId = AL.GenBuffer(),
SampleCount = GetSampleCount(buffer),
};
@ -85,17 +86,22 @@ namespace Ryujinx.Audio.Backends.OpenAL
public override void SetVolume(float volume)
{
lock (_lock)
{
AL.Source(_sourceId, ALSourcef.Gain, volume);
}
_volume = volume;
UpdateMasterVolume(_driver.Volume);
}
public override float GetVolume()
{
AL.GetSource(_sourceId, ALSourcef.Gain, out float volume);
return _volume;
}
return volume;
public void UpdateMasterVolume(float newVolume)
{
lock (_lock)
{
AL.Source(_sourceId, ALSourcef.Gain, newVolume * _volume);
}
}
public override void Start()
@ -131,7 +137,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
return true;
}
return driverBuffer.DriverIdentifier != buffer.HostTag;
return driverBuffer.DriverIdentifier != buffer.DataPointer;
}
}

View file

@ -20,11 +20,13 @@ namespace Ryujinx.Audio.Backends.SDL2
private readonly bool _supportSurroundConfiguration;
public float Volume { get; set; }
// TODO: Add this to SDL2-CS
// NOTE: We use a DllImport here because of marshaling issue for spec.
#pragma warning disable SYSLIB1054
[DllImport("SDL2.framework/SDL2")]
private static extern int SDL_GetDefaultAudioInfo(IntPtr name, out SDL_AudioSpec spec, int isCapture);
[DllImport("SDL2")]
private static extern int SDL_GetDefaultAudioInfo(nint name, out SDL_AudioSpec spec, int isCapture);
#pragma warning restore SYSLIB1054
public SDL2HardwareDeviceDriver()
@ -35,7 +37,7 @@ namespace Ryujinx.Audio.Backends.SDL2
SDL2Driver.Instance.Initialize();
int res = SDL_GetDefaultAudioInfo(IntPtr.Zero, out var spec, 0);
int res = SDL_GetDefaultAudioInfo(nint.Zero, out var spec, 0);
if (res != 0)
{
@ -48,6 +50,8 @@ namespace Ryujinx.Audio.Backends.SDL2
{
_supportSurroundConfiguration = spec.channels >= 6;
}
Volume = 1f;
}
public static bool IsSupported => IsSupportedInternal();
@ -74,7 +78,7 @@ namespace Ryujinx.Audio.Backends.SDL2
return _pauseEvent;
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
{
@ -91,7 +95,7 @@ namespace Ryujinx.Audio.Backends.SDL2
throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
}
SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0);
@ -132,7 +136,7 @@ namespace Ryujinx.Audio.Backends.SDL2
desired.callback = callback;
uint device = SDL_OpenAudioDevice(IntPtr.Zero, 0, ref desired, out SDL_AudioSpec got, 0);
uint device = SDL_OpenAudioDevice(nint.Zero, 0, ref desired, out SDL_AudioSpec got, 0);
if (device == 0)
{

View file

@ -1,8 +1,10 @@
using Ryujinx.Audio.Backends.Common;
using Ryujinx.Audio.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Memory;
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Threading;
@ -26,7 +28,7 @@ namespace Ryujinx.Audio.Backends.SDL2
private float _volume;
private readonly ushort _nativeSampleFormat;
public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_driver = driver;
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
@ -37,7 +39,7 @@ namespace Ryujinx.Audio.Backends.SDL2
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
_sampleCount = uint.MaxValue;
_started = false;
_volume = requestedVolume;
_volume = 1f;
}
private void EnsureAudioStreamSetup(AudioBuffer buffer)
@ -70,7 +72,7 @@ namespace Ryujinx.Audio.Backends.SDL2
}
}
private unsafe void Update(IntPtr userdata, IntPtr stream, int streamLength)
private unsafe void Update(nint userdata, nint stream, int streamLength)
{
Span<byte> streamSpan = new((void*)stream, streamLength);
@ -87,19 +89,21 @@ namespace Ryujinx.Audio.Backends.SDL2
return;
}
byte[] samples = new byte[frameCount * _bytesPerFrame];
using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * _bytesPerFrame);
Span<byte> samples = samplesOwner.Span;
_ringBuffer.Read(samples, 0, samples.Length);
fixed (byte* p = samples)
{
IntPtr pStreamSrc = (IntPtr)p;
nint pStreamSrc = (nint)p;
// Zero the dest buffer
streamSpan.Clear();
// Apply volume to written data
SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME));
SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_driver.Volume * _volume * SDL_MIX_MAXVOLUME));
}
ulong sampleCount = GetSampleCount(samples.Length);
@ -151,7 +155,7 @@ namespace Ryujinx.Audio.Backends.SDL2
if (_outputStream != 0)
{
SDL2AudioBuffer driverBuffer = new(buffer.HostTag, GetSampleCount(buffer));
SDL2AudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer));
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
@ -205,7 +209,7 @@ namespace Ryujinx.Audio.Backends.SDL2
return true;
}
return driverBuffer.DriverIdentifier != buffer.HostTag;
return driverBuffer.DriverIdentifier != buffer.DataPointer;
}
protected virtual void Dispose(bool disposing)

View file

@ -10,41 +10,41 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
private const string LibraryName = "libsoundio";
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void OnDeviceChangeNativeDelegate(IntPtr ctx);
public delegate void OnDeviceChangeNativeDelegate(nint ctx);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void OnBackendDisconnectedDelegate(IntPtr ctx, SoundIoError err);
public delegate void OnBackendDisconnectedDelegate(nint ctx, SoundIoError err);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void OnEventsSignalDelegate(IntPtr ctx);
public delegate void OnEventsSignalDelegate(nint ctx);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void EmitRtPrioWarningDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void JackCallbackDelegate(IntPtr msg);
public delegate void JackCallbackDelegate(nint msg);
[StructLayout(LayoutKind.Sequential)]
public struct SoundIoStruct
{
public IntPtr UserData;
public IntPtr OnDeviceChange;
public IntPtr OnBackendDisconnected;
public IntPtr OnEventsSignal;
public nint UserData;
public nint OnDeviceChange;
public nint OnBackendDisconnected;
public nint OnEventsSignal;
public SoundIoBackend CurrentBackend;
public IntPtr ApplicationName;
public IntPtr EmitRtPrioWarning;
public IntPtr JackInfoCallback;
public IntPtr JackErrorCallback;
public nint ApplicationName;
public nint EmitRtPrioWarning;
public nint JackInfoCallback;
public nint JackErrorCallback;
}
public struct SoundIoChannelLayout
{
public IntPtr Name;
public nint Name;
public int ChannelCount;
public Array24<SoundIoChannelId> Channels;
public static IntPtr GetDefault(int channelCount)
public static nint GetDefault(int channelCount)
{
return soundio_channel_layout_get_default(channelCount);
}
@ -63,17 +63,17 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
public struct SoundIoDevice
{
public IntPtr SoundIo;
public IntPtr Id;
public IntPtr Name;
public nint SoundIo;
public nint Id;
public nint Name;
public SoundIoDeviceAim Aim;
public IntPtr Layouts;
public nint Layouts;
public int LayoutCount;
public SoundIoChannelLayout CurrentLayout;
public IntPtr Formats;
public nint Formats;
public int FormatCount;
public SoundIoFormat CurrentFormat;
public IntPtr SampleRates;
public nint SampleRates;
public int SampleRateCount;
public int SampleRateCurrent;
public double SoftwareLatencyMin;
@ -86,17 +86,17 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
public struct SoundIoOutStream
{
public IntPtr Device;
public nint Device;
public SoundIoFormat Format;
public int SampleRate;
public SoundIoChannelLayout Layout;
public double SoftwareLatency;
public float Volume;
public IntPtr UserData;
public IntPtr WriteCallback;
public IntPtr UnderflowCallback;
public IntPtr ErrorCallback;
public IntPtr Name;
public nint UserData;
public nint WriteCallback;
public nint UnderflowCallback;
public nint ErrorCallback;
public nint Name;
public bool NonTerminalHint;
public int BytesPerFrame;
public int BytesPerSample;
@ -105,74 +105,74 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
public struct SoundIoChannelArea
{
public IntPtr Pointer;
public nint Pointer;
public int Step;
}
[LibraryImport(LibraryName)]
internal static partial IntPtr soundio_create();
internal static partial nint soundio_create();
[LibraryImport(LibraryName)]
internal static partial SoundIoError soundio_connect(IntPtr ctx);
internal static partial SoundIoError soundio_connect(nint ctx);
[LibraryImport(LibraryName)]
internal static partial void soundio_disconnect(IntPtr ctx);
internal static partial void soundio_disconnect(nint ctx);
[LibraryImport(LibraryName)]
internal static partial void soundio_flush_events(IntPtr ctx);
internal static partial void soundio_flush_events(nint ctx);
[LibraryImport(LibraryName)]
internal static partial int soundio_output_device_count(IntPtr ctx);
internal static partial int soundio_output_device_count(nint ctx);
[LibraryImport(LibraryName)]
internal static partial int soundio_default_output_device_index(IntPtr ctx);
internal static partial int soundio_default_output_device_index(nint ctx);
[LibraryImport(LibraryName)]
internal static partial IntPtr soundio_get_output_device(IntPtr ctx, int index);
internal static partial nint soundio_get_output_device(nint ctx, int index);
[LibraryImport(LibraryName)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool soundio_device_supports_format(IntPtr devCtx, SoundIoFormat format);
internal static partial bool soundio_device_supports_format(nint devCtx, SoundIoFormat format);
[LibraryImport(LibraryName)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool soundio_device_supports_layout(IntPtr devCtx, IntPtr layout);
internal static partial bool soundio_device_supports_layout(nint devCtx, nint layout);
[LibraryImport(LibraryName)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool soundio_device_supports_sample_rate(IntPtr devCtx, int sampleRate);
internal static partial bool soundio_device_supports_sample_rate(nint devCtx, int sampleRate);
[LibraryImport(LibraryName)]
internal static partial IntPtr soundio_outstream_create(IntPtr devCtx);
internal static partial nint soundio_outstream_create(nint devCtx);
[LibraryImport(LibraryName)]
internal static partial SoundIoError soundio_outstream_open(IntPtr outStreamCtx);
internal static partial SoundIoError soundio_outstream_open(nint outStreamCtx);
[LibraryImport(LibraryName)]
internal static partial SoundIoError soundio_outstream_start(IntPtr outStreamCtx);
internal static partial SoundIoError soundio_outstream_start(nint outStreamCtx);
[LibraryImport(LibraryName)]
internal static partial SoundIoError soundio_outstream_begin_write(IntPtr outStreamCtx, IntPtr areas, IntPtr frameCount);
internal static partial SoundIoError soundio_outstream_begin_write(nint outStreamCtx, nint areas, nint frameCount);
[LibraryImport(LibraryName)]
internal static partial SoundIoError soundio_outstream_end_write(IntPtr outStreamCtx);
internal static partial SoundIoError soundio_outstream_end_write(nint outStreamCtx);
[LibraryImport(LibraryName)]
internal static partial SoundIoError soundio_outstream_pause(IntPtr devCtx, [MarshalAs(UnmanagedType.Bool)] bool pause);
internal static partial SoundIoError soundio_outstream_pause(nint devCtx, [MarshalAs(UnmanagedType.Bool)] bool pause);
[LibraryImport(LibraryName)]
internal static partial SoundIoError soundio_outstream_set_volume(IntPtr devCtx, double volume);
internal static partial SoundIoError soundio_outstream_set_volume(nint devCtx, double volume);
[LibraryImport(LibraryName)]
internal static partial void soundio_outstream_destroy(IntPtr streamCtx);
internal static partial void soundio_outstream_destroy(nint streamCtx);
[LibraryImport(LibraryName)]
internal static partial void soundio_destroy(IntPtr ctx);
internal static partial void soundio_destroy(nint ctx);
[LibraryImport(LibraryName)]
internal static partial IntPtr soundio_channel_layout_get_default(int channelCount);
internal static partial nint soundio_channel_layout_get_default(int channelCount);
[LibraryImport(LibraryName)]
internal static partial IntPtr soundio_strerror(SoundIoError err);
internal static partial nint soundio_strerror(SoundIoError err);
}
}

View file

@ -8,13 +8,13 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
{
public class SoundIoContext : IDisposable
{
private IntPtr _context;
private nint _context;
private Action<SoundIoError> _onBackendDisconnect;
private OnBackendDisconnectedDelegate _onBackendDisconnectNative;
public IntPtr Context => _context;
public nint Context => _context;
internal SoundIoContext(IntPtr context)
internal SoundIoContext(nint context)
{
_context = context;
_onBackendDisconnect = null;
@ -60,9 +60,9 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
public SoundIoDeviceContext GetOutputDevice(int index)
{
IntPtr deviceContext = soundio_get_output_device(_context, index);
nint deviceContext = soundio_get_output_device(_context, index);
if (deviceContext == IntPtr.Zero)
if (deviceContext == nint.Zero)
{
return null;
}
@ -72,9 +72,9 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
public static SoundIoContext Create()
{
IntPtr context = soundio_create();
nint context = soundio_create();
if (context == IntPtr.Zero)
if (context == nint.Zero)
{
return null;
}
@ -84,9 +84,9 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
protected virtual void Dispose(bool disposing)
{
IntPtr currentContext = Interlocked.Exchange(ref _context, IntPtr.Zero);
nint currentContext = Interlocked.Exchange(ref _context, nint.Zero);
if (currentContext != IntPtr.Zero)
if (currentContext != nint.Zero)
{
soundio_destroy(currentContext);
}

View file

@ -7,11 +7,11 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
{
public class SoundIoDeviceContext
{
private readonly IntPtr _context;
private readonly nint _context;
public IntPtr Context => _context;
public nint Context => _context;
internal SoundIoDeviceContext(IntPtr context)
internal SoundIoDeviceContext(nint context)
{
_context = context;
}
@ -36,9 +36,9 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
public SoundIoOutStreamContext CreateOutStream()
{
IntPtr context = soundio_outstream_create(_context);
nint context = soundio_outstream_create(_context);
if (context == IntPtr.Zero)
if (context == nint.Zero)
{
return null;
}

View file

@ -8,19 +8,19 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
public class SoundIoOutStreamContext : IDisposable
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private unsafe delegate void WriteCallbackDelegate(IntPtr ctx, int frameCountMin, int frameCountMax);
private unsafe delegate void WriteCallbackDelegate(nint ctx, int frameCountMin, int frameCountMax);
private IntPtr _context;
private IntPtr _nameStored;
private nint _context;
private nint _nameStored;
private Action<int, int> _writeCallback;
private WriteCallbackDelegate _writeCallbackNative;
public IntPtr Context => _context;
public nint Context => _context;
internal SoundIoOutStreamContext(IntPtr context)
internal SoundIoOutStreamContext(nint context)
{
_context = context;
_nameStored = IntPtr.Zero;
_nameStored = nint.Zero;
_writeCallback = null;
_writeCallbackNative = null;
}
@ -40,7 +40,7 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
{
var context = GetOutContext();
if (_nameStored != IntPtr.Zero && context.Name == _nameStored)
if (_nameStored != nint.Zero && context.Name == _nameStored)
{
Marshal.FreeHGlobal(_nameStored);
}
@ -124,14 +124,14 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
public Span<SoundIoChannelArea> BeginWrite(ref int frameCount)
{
IntPtr arenas = default;
nint arenas = default;
int nativeFrameCount = frameCount;
unsafe
{
var frameCountPtr = &nativeFrameCount;
var arenasPtr = &arenas;
CheckError(soundio_outstream_begin_write(_context, (IntPtr)arenasPtr, (IntPtr)frameCountPtr));
CheckError(soundio_outstream_begin_write(_context, (nint)arenasPtr, (nint)frameCountPtr));
frameCount = *frameCountPtr;
@ -143,10 +143,10 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
protected virtual void Dispose(bool disposing)
{
if (_context != IntPtr.Zero)
if (_context != nint.Zero)
{
soundio_outstream_destroy(_context);
_context = IntPtr.Zero;
_context = nint.Zero;
}
}

View file

@ -11,15 +11,15 @@
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dll" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dll" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.dll</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'">
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.dylib</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.so</TargetPath>
</ContentWithTargetPath>

View file

@ -19,6 +19,25 @@ namespace Ryujinx.Audio.Backends.SoundIo
private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
private int _disposeState;
private float _volume = 1f;
public float Volume
{
get
{
return _volume;
}
set
{
_volume = value;
foreach (SoundIoHardwareDeviceSession session in _sessions.Keys)
{
session.UpdateMasterVolume(value);
}
}
}
public SoundIoHardwareDeviceDriver()
{
_audioContext = SoundIoContext.Create();
@ -122,7 +141,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
return _pauseEvent;
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
{
@ -134,14 +153,12 @@ namespace Ryujinx.Audio.Backends.SoundIo
sampleRate = Constants.TargetSampleRate;
}
volume = Math.Clamp(volume, 0, 1);
if (direction != Direction.Output)
{
throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!");
}
SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0);

View file

@ -1,8 +1,10 @@
using Ryujinx.Audio.Backends.Common;
using Ryujinx.Audio.Backends.SoundIo.Native;
using Ryujinx.Audio.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Memory;
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading;
@ -18,16 +20,18 @@ namespace Ryujinx.Audio.Backends.SoundIo
private readonly DynamicRingBuffer _ringBuffer;
private ulong _playedSampleCount;
private readonly ManualResetEvent _updateRequiredEvent;
private float _volume;
private int _disposeState;
public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_driver = driver;
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
_queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
_ringBuffer = new DynamicRingBuffer();
_volume = 1f;
SetupOutputStream(requestedVolume);
SetupOutputStream(driver.Volume);
}
private void SetupOutputStream(float requestedVolume)
@ -35,7 +39,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
_outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount);
_outputStream.WriteCallback += Update;
_outputStream.Volume = requestedVolume;
// TODO: Setup other callbacks (errors, ect).
// TODO: Setup other callbacks (errors, etc.)
_outputStream.Open();
}
@ -47,14 +51,14 @@ namespace Ryujinx.Audio.Backends.SoundIo
public override float GetVolume()
{
return _outputStream.Volume;
return _volume;
}
public override void PrepareToClose() { }
public override void QueueBuffer(AudioBuffer buffer)
{
SoundIoAudioBuffer driverBuffer = new(buffer.HostTag, GetSampleCount(buffer));
SoundIoAudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer));
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
@ -63,7 +67,14 @@ namespace Ryujinx.Audio.Backends.SoundIo
public override void SetVolume(float volume)
{
_outputStream.SetVolume(volume);
_volume = volume;
_outputStream.SetVolume(_driver.Volume * volume);
}
public void UpdateMasterVolume(float newVolume)
{
_outputStream.SetVolume(newVolume * _volume);
}
public override void Start()
@ -90,7 +101,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
return true;
}
return driverBuffer.DriverIdentifier != buffer.HostTag;
return driverBuffer.DriverIdentifier != buffer.DataPointer;
}
private unsafe void Update(int minFrameCount, int maxFrameCount)
@ -111,7 +122,9 @@ namespace Ryujinx.Audio.Backends.SoundIo
int channelCount = areas.Length;
byte[] samples = new byte[frameCount * bytesPerFrame];
using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * bytesPerFrame);
Span<byte> samples = samplesOwner.Span;
_ringBuffer.Read(samples, 0, samples.Length);

View file

@ -1,5 +1,7 @@
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System;
using System.Buffers;
namespace Ryujinx.Audio.Backends.Common
{
@ -12,7 +14,8 @@ namespace Ryujinx.Audio.Backends.Common
private readonly object _lock = new();
private byte[] _buffer;
private MemoryOwner<byte> _bufferOwner;
private Memory<byte> _buffer;
private int _size;
private int _headOffset;
private int _tailOffset;
@ -21,7 +24,8 @@ namespace Ryujinx.Audio.Backends.Common
public DynamicRingBuffer(int initialCapacity = RingBufferAlignment)
{
_buffer = new byte[initialCapacity];
_bufferOwner = MemoryOwner<byte>.RentCleared(initialCapacity);
_buffer = _bufferOwner.Memory;
}
public void Clear()
@ -33,6 +37,11 @@ namespace Ryujinx.Audio.Backends.Common
public void Clear(int size)
{
if (size == 0)
{
return;
}
lock (_lock)
{
if (size > _size)
@ -40,11 +49,6 @@ namespace Ryujinx.Audio.Backends.Common
size = _size;
}
if (size == 0)
{
return;
}
_headOffset = (_headOffset + size) % _buffer.Length;
_size -= size;
@ -58,28 +62,31 @@ namespace Ryujinx.Audio.Backends.Common
private void SetCapacityLocked(int capacity)
{
byte[] buffer = new byte[capacity];
MemoryOwner<byte> newBufferOwner = MemoryOwner<byte>.RentCleared(capacity);
Memory<byte> newBuffer = newBufferOwner.Memory;
if (_size > 0)
{
if (_headOffset < _tailOffset)
{
Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _size);
_buffer.Slice(_headOffset, _size).CopyTo(newBuffer);
}
else
{
Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _buffer.Length - _headOffset);
Buffer.BlockCopy(_buffer, 0, buffer, _buffer.Length - _headOffset, _tailOffset);
_buffer[_headOffset..].CopyTo(newBuffer);
_buffer[.._tailOffset].CopyTo(newBuffer[(_buffer.Length - _headOffset)..]);
}
}
_buffer = buffer;
_bufferOwner.Dispose();
_bufferOwner = newBufferOwner;
_buffer = newBuffer;
_headOffset = 0;
_tailOffset = _size;
}
public void Write<T>(T[] buffer, int index, int count)
public void Write(ReadOnlySpan<byte> buffer, int index, int count)
{
if (count == 0)
{
@ -99,17 +106,17 @@ namespace Ryujinx.Audio.Backends.Common
if (tailLength >= count)
{
Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count);
buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]);
}
else
{
Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, tailLength);
Buffer.BlockCopy(buffer, index + tailLength, _buffer, 0, count - tailLength);
buffer.Slice(index, tailLength).CopyTo(_buffer.Span[_tailOffset..]);
buffer.Slice(index + tailLength, count - tailLength).CopyTo(_buffer.Span);
}
}
else
{
Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count);
buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]);
}
_size += count;
@ -117,8 +124,13 @@ namespace Ryujinx.Audio.Backends.Common
}
}
public int Read<T>(T[] buffer, int index, int count)
public int Read(Span<byte> buffer, int index, int count)
{
if (count == 0)
{
return 0;
}
lock (_lock)
{
if (count > _size)
@ -126,14 +138,9 @@ namespace Ryujinx.Audio.Backends.Common
count = _size;
}
if (count == 0)
{
return 0;
}
if (_headOffset < _tailOffset)
{
Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count);
_buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]);
}
else
{
@ -141,12 +148,12 @@ namespace Ryujinx.Audio.Backends.Common
if (tailLength >= count)
{
Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count);
_buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]);
}
else
{
Buffer.BlockCopy(_buffer, _headOffset, buffer, index, tailLength);
Buffer.BlockCopy(_buffer, 0, buffer, index + tailLength, count - tailLength);
_buffer.Span.Slice(_headOffset, tailLength).CopyTo(buffer[index..]);
_buffer.Span[..(count - tailLength)].CopyTo(buffer[(index + tailLength)..]);
}
}

View file

@ -40,7 +40,7 @@ namespace Ryujinx.Audio.Backends.Common
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public virtual ulong GetSampleCount(int dataSize)
protected ulong GetSampleCount(int dataSize)
{
return (ulong)BackendHelper.GetSampleCount(RequestedSampleFormat, (int)RequestedChannelCount, dataSize);
}

View file

@ -16,6 +16,12 @@ namespace Ryujinx.Audio.Backends.CompatLayer
public static bool IsSupported => true;
public float Volume
{
get => _realDriver.Volume;
set => _realDriver.Volume = value;
}
public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice)
{
_realDriver = realDevice;
@ -90,7 +96,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
throw new ArgumentException("No valid sample format configuration found!");
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
{
@ -102,8 +108,6 @@ namespace Ryujinx.Audio.Backends.CompatLayer
sampleRate = Constants.TargetSampleRate;
}
volume = Math.Clamp(volume, 0, 1);
if (!_realDriver.SupportsDirection(direction))
{
if (direction == Direction.Input)
@ -119,7 +123,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat);
uint hardwareChannelCount = SelectHardwareChannelCount(channelCount);
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount, volume);
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount);
if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat)
{

View file

@ -39,11 +39,6 @@ namespace Ryujinx.Audio.Backends.CompatLayer
_realSession.PrepareToClose();
}
public override ulong GetSampleCount(int dataSize)
{
return _realSession.GetSampleCount(dataSize);
}
public override void QueueBuffer(AudioBuffer buffer)
{
SampleFormat realSampleFormat = _realSession.RequestedSampleFormat;
@ -124,7 +119,6 @@ namespace Ryujinx.Audio.Backends.CompatLayer
AudioBuffer fakeBuffer = new()
{
BufferTag = buffer.BufferTag,
HostTag = buffer.HostTag,
DataPointer = buffer.DataPointer,
DataSize = (ulong)samples.Length,
};

View file

@ -31,7 +31,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
private const int Minus6dBInQ15 = (int)(0.501f * RawQ15One);
private const int Minus12dBInQ15 = (int)(0.251f * RawQ15One);
private static readonly int[] _defaultSurroundToStereoCoefficients = new int[4]
private static readonly long[] _defaultSurroundToStereoCoefficients = new long[4]
{
RawQ15One,
Minus3dBInQ15,
@ -39,7 +39,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
Minus3dBInQ15,
};
private static readonly int[] _defaultStereoToMonoCoefficients = new int[2]
private static readonly long[] _defaultStereoToMonoCoefficients = new long[2]
{
Minus6dBInQ15,
Minus6dBInQ15,
@ -62,19 +62,23 @@ namespace Ryujinx.Audio.Backends.CompatLayer
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static short DownMixStereoToMono(ReadOnlySpan<int> coefficients, short left, short right)
private static short DownMixStereoToMono(ReadOnlySpan<long> coefficients, short left, short right)
{
return (short)((left * coefficients[0] + right * coefficients[1]) >> Q15Bits);
return (short)Math.Clamp((left * coefficients[0] + right * coefficients[1]) >> Q15Bits, short.MinValue, short.MaxValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static short DownMixSurroundToStereo(ReadOnlySpan<int> coefficients, short back, short lfe, short center, short front)
private static short DownMixSurroundToStereo(ReadOnlySpan<long> coefficients, short back, short lfe, short center, short front)
{
return (short)((coefficients[3] * back + coefficients[2] * lfe + coefficients[1] * center + coefficients[0] * front + RawQ15HalfOne) >> Q15Bits);
return (short)Math.Clamp(
(coefficients[3] * back +
coefficients[2] * lfe +
coefficients[1] * center +
coefficients[0] * front + RawQ15HalfOne) >> Q15Bits, short.MinValue, short.MaxValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static short[] DownMixSurroundToStereo(ReadOnlySpan<int> coefficients, ReadOnlySpan<short> data)
private static short[] DownMixSurroundToStereo(ReadOnlySpan<long> coefficients, ReadOnlySpan<short> data)
{
int samplePerChannelCount = data.Length / SurroundChannelCount;
@ -94,7 +98,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static short[] DownMixStereoToMono(ReadOnlySpan<int> coefficients, ReadOnlySpan<short> data)
private static short[] DownMixStereoToMono(ReadOnlySpan<long> coefficients, ReadOnlySpan<short> data)
{
int samplePerChannelCount = data.Length / StereoChannelCount;

View file

@ -1,86 +0,0 @@
using Ryujinx.Audio.Backends.Common;
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Integration;
using Ryujinx.Memory;
using System;
using System.Threading;
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
namespace Ryujinx.Audio.Backends.DelayLayer
{
public class DelayLayerHardwareDeviceDriver : IHardwareDeviceDriver
{
private readonly IHardwareDeviceDriver _realDriver;
public static bool IsSupported => true;
public ulong SampleDelay48k;
public DelayLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice, ulong sampleDelay48k)
{
_realDriver = realDevice;
SampleDelay48k = sampleDelay48k;
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
{
IHardwareDeviceSession session = _realDriver.OpenDeviceSession(direction, memoryManager, sampleFormat, sampleRate, channelCount, volume);
if (direction == Direction.Output)
{
return new DelayLayerHardwareDeviceSession(this, session as HardwareDeviceSessionOutputBase, sampleFormat, channelCount);
}
return session;
}
public ManualResetEvent GetUpdateRequiredEvent()
{
return _realDriver.GetUpdateRequiredEvent();
}
public ManualResetEvent GetPauseEvent()
{
return _realDriver.GetPauseEvent();
}
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_realDriver.Dispose();
}
}
public bool SupportsSampleRate(uint sampleRate)
{
return _realDriver.SupportsSampleRate(sampleRate);
}
public bool SupportsSampleFormat(SampleFormat sampleFormat)
{
return _realDriver.SupportsSampleFormat(sampleFormat);
}
public bool SupportsDirection(Direction direction)
{
return _realDriver.SupportsDirection(direction);
}
public bool SupportsChannelCount(uint channelCount)
{
return _realDriver.SupportsChannelCount(channelCount);
}
public IHardwareDeviceDriver GetRealDeviceDriver()
{
return _realDriver.GetRealDeviceDriver();
}
}
}

View file

@ -1,151 +0,0 @@
using Ryujinx.Audio.Backends.Common;
using Ryujinx.Audio.Common;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Audio.Backends.DelayLayer
{
internal class DelayLayerHardwareDeviceSession : HardwareDeviceSessionOutputBase
{
private readonly HardwareDeviceSessionOutputBase _realSession;
private readonly ManualResetEvent _updateRequiredEvent;
private readonly ulong _delayTarget;
private object _sampleCountLock = new();
private List<AudioBuffer> _buffers = new();
public DelayLayerHardwareDeviceSession(DelayLayerHardwareDeviceDriver driver, HardwareDeviceSessionOutputBase realSession, SampleFormat userSampleFormat, uint userChannelCount) : base(realSession.MemoryManager, realSession.RequestedSampleFormat, realSession.RequestedSampleRate, userChannelCount)
{
_realSession = realSession;
_delayTarget = driver.SampleDelay48k;
_updateRequiredEvent = driver.GetUpdateRequiredEvent();
}
public override void Dispose()
{
_realSession.Dispose();
}
public override ulong GetPlayedSampleCount()
{
lock (_sampleCountLock)
{
// Update the played samples count.
WasBufferFullyConsumed(null);
return _playedSamplesCount;
}
}
public override float GetVolume()
{
return _realSession.GetVolume();
}
public override void PrepareToClose()
{
_realSession.PrepareToClose();
}
public override void QueueBuffer(AudioBuffer buffer)
{
_realSession.QueueBuffer(buffer);
ulong samples = GetSampleCount(buffer);
lock (_sampleCountLock)
{
_buffers.Add(buffer);
}
_updateRequiredEvent.Set();
}
public override ulong GetSampleCount(int dataSize)
{
return _realSession.GetSampleCount(dataSize);
}
public override void SetVolume(float volume)
{
_realSession.SetVolume(volume);
}
public override void Start()
{
_realSession.Start();
}
public override void Stop()
{
_realSession.Stop();
}
private ulong _playedSamplesCount = 0;
private int _frontIndex = -1;
public override bool WasBufferFullyConsumed(AudioBuffer buffer)
{
ulong delaySamples = 0;
bool isConsumed = true;
// True if it's in the _delayedSamples range.
lock (_sampleCountLock)
{
for (int i = 0; i < _buffers.Count; i++)
{
AudioBuffer elem = _buffers[i];
isConsumed = isConsumed && _realSession.WasBufferFullyConsumed(elem);
ulong samples = GetSampleCount(elem);
bool afterFront = i > _frontIndex;
if (isConsumed)
{
if (_frontIndex > -1)
{
_frontIndex--;
}
_buffers.RemoveAt(i--);
if (afterFront)
{
_playedSamplesCount += samples;
}
if (buffer == elem)
{
return true;
}
}
else
{
if (afterFront && delaySamples < _delayTarget)
{
_playedSamplesCount += samples;
_frontIndex = i;
}
if (buffer == elem)
{
return i <= _frontIndex;
}
delaySamples += samples;
}
}
// Buffer was not queued.
return true;
}
}
public override bool RegisterBuffer(AudioBuffer buffer, byte[] samples)
{
return _realSession.RegisterBuffer(buffer, samples);
}
}
}

View file

@ -14,13 +14,17 @@ namespace Ryujinx.Audio.Backends.Dummy
public static bool IsSupported => true;
public float Volume { get; set; }
public DummyHardwareDeviceDriver()
{
_updateRequiredEvent = new ManualResetEvent(false);
_pauseEvent = new ManualResetEvent(true);
Volume = 1f;
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (sampleRate == 0)
{
@ -34,7 +38,7 @@ namespace Ryujinx.Audio.Backends.Dummy
if (direction == Direction.Output)
{
return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount);
}
return new DummyHardwareDeviceSessionInput(this, memoryManager);

View file

@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Backends.Dummy
private ulong _playedSampleCount;
public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_volume = requestedVolume;
_volume = 1f;
_manager = manager;
}

View file

@ -1,5 +1,4 @@
using Ryujinx.Audio.Integration;
using System.Threading;
namespace Ryujinx.Audio.Common
{
@ -8,19 +7,12 @@ namespace Ryujinx.Audio.Common
/// </summary>
public class AudioBuffer
{
private static ulong UniqueIdGlobal = 0;
/// <summary>
/// Unique tag of this buffer, from the guest.
/// Unique tag of this buffer.
/// </summary>
/// <remarks>Unique per session</remarks>
public ulong BufferTag;
/// <summary>
/// Globally unique ID of the buffer on the host.
/// </summary>
public ulong HostTag = Interlocked.Increment(ref UniqueIdGlobal);
/// <summary>
/// Pointer to the user samples.
/// </summary>

View file

@ -166,7 +166,6 @@ namespace Ryujinx.Audio.Input
/// </summary>
/// <param name="filtered">If true, filter disconnected devices</param>
/// <returns>The list of all audio inputs name</returns>
#pragma warning disable CA1822 // Mark member as static
public string[] ListAudioIns(bool filtered)
{
if (filtered)
@ -176,7 +175,6 @@ namespace Ryujinx.Audio.Input
return new[] { Constants.DefaultDeviceInputName };
}
#pragma warning restore CA1822
/// <summary>
/// Open a new <see cref="AudioInputSystem"/>.
@ -188,8 +186,6 @@ namespace Ryujinx.Audio.Input
/// <param name="inputDeviceName">The input device name wanted by the user</param>
/// <param name="sampleFormat">The sample format to use</param>
/// <param name="parameter">The user configuration</param>
/// <param name="appletResourceUserId">The applet resource user id of the application</param>
/// <param name="processHandle">The process handle of the application</param>
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
public ResultCode OpenAudioIn(out string outputDeviceName,
out AudioOutputConfiguration outputConfiguration,
@ -197,9 +193,7 @@ namespace Ryujinx.Audio.Input
IVirtualMemoryManager memoryManager,
string inputDeviceName,
SampleFormat sampleFormat,
ref AudioInputConfiguration parameter,
ulong appletResourceUserId,
uint processHandle)
ref AudioInputConfiguration parameter)
{
int sessionId = AcquireSessionId();

View file

@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Integration
private readonly byte[] _buffer;
public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate, float volume)
public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate)
{
_session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount, volume);
_session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount);
_channelCount = channelCount;
_sampleRate = sampleRate;
_currentBufferTag = 0;

View file

@ -16,7 +16,9 @@ namespace Ryujinx.Audio.Integration
Output,
}
IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume = 1f);
float Volume { get; set; }
IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount);
ManualResetEvent GetUpdateRequiredEvent();
ManualResetEvent GetPauseEvent();

View file

@ -165,12 +165,10 @@ namespace Ryujinx.Audio.Output
/// Get the list of all audio outputs name.
/// </summary>
/// <returns>The list of all audio outputs name</returns>
#pragma warning disable CA1822 // Mark member as static
public string[] ListAudioOuts()
{
return new[] { Constants.DefaultDeviceOutputName };
}
#pragma warning restore CA1822
/// <summary>
/// Open a new <see cref="AudioOutputSystem"/>.
@ -182,9 +180,6 @@ namespace Ryujinx.Audio.Output
/// <param name="inputDeviceName">The input device name wanted by the user</param>
/// <param name="sampleFormat">The sample format to use</param>
/// <param name="parameter">The user configuration</param>
/// <param name="appletResourceUserId">The applet resource user id of the application</param>
/// <param name="processHandle">The process handle of the application</param>
/// <param name="volume">The volume level to request</param>
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
public ResultCode OpenAudioOut(out string outputDeviceName,
out AudioOutputConfiguration outputConfiguration,
@ -192,16 +187,13 @@ namespace Ryujinx.Audio.Output
IVirtualMemoryManager memoryManager,
string inputDeviceName,
SampleFormat sampleFormat,
ref AudioInputConfiguration parameter,
ulong appletResourceUserId,
uint processHandle,
float volume)
ref AudioInputConfiguration parameter)
{
int sessionId = AcquireSessionId();
_sessionsBufferEvents[sessionId].Clear();
IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount, volume);
IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount);
AudioOutputSystem audioOut = new(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]);
@ -234,41 +226,6 @@ namespace Ryujinx.Audio.Output
return result;
}
/// <summary>
/// Sets the volume for all output devices.
/// </summary>
/// <param name="volume">The volume to set.</param>
public void SetVolume(float volume)
{
if (_sessions != null)
{
foreach (AudioOutputSystem session in _sessions)
{
session?.SetVolume(volume);
}
}
}
/// <summary>
/// Gets the volume for all output devices.
/// </summary>
/// <returns>A float indicating the volume level.</returns>
public float GetVolume()
{
if (_sessions != null)
{
foreach (AudioOutputSystem session in _sessions)
{
if (session != null)
{
return session.GetVolume();
}
}
}
return 0.0f;
}
public void Dispose()
{
GC.SuppressFinalize(this);

View file

@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Common
public ulong Flags;
/// <summary>
/// Represents an error during <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.ReadOnlyMemory{byte})"/>.
/// Represents an error during <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.Buffers.ReadOnlySequence{byte})"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ErrorInfo

View file

@ -4,7 +4,7 @@ using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Common
{
/// <summary>
/// Update data header used for input and output of <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.ReadOnlyMemory{byte})"/>.
/// Update data header used for input and output of <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.Buffers.ReadOnlySequence{byte})"/>.
/// </summary>
public struct UpdateDataHeader
{

View file

@ -15,7 +15,6 @@ namespace Ryujinx.Audio.Renderer.Common
{
public const int Align = 0x10;
public const int BiquadStateOffset = 0x0;
public const int BiquadStateSize = 0x10;
/// <summary>
/// The state of the biquad filters of this voice.

View file

@ -81,7 +81,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static short GetCoefficientAtIndex(ReadOnlySpan<short> coefficients, int index)
{
if ((uint)index > (uint)coefficients.Length)
if ((uint)index >= (uint)coefficients.Length)
{
Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}");

Some files were not shown because too many files have changed in this diff Show more