mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2024-12-28 19:43:04 +00:00
Merge remote-tracking branch 'upstream/master' into fluent-system-icons
This commit is contained in:
commit
acf7395c49
356 changed files with 19620 additions and 7301 deletions
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
|
@ -1,6 +1,7 @@
|
|||
name: Feature Request
|
||||
description: Suggest a new feature for Ryujinx.
|
||||
title: "[Feature Request]"
|
||||
labels: enhancement
|
||||
body:
|
||||
- type: textarea
|
||||
id: overview
|
||||
|
|
18
.github/workflows/build.yml
vendored
18
.github/workflows/build.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
|||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||
- { name: osx-x64, os: macOS-latest, zip_os_name: osx_x64 }
|
||||
- { name: osx-x64, os: macos-13, zip_os_name: osx_x64 }
|
||||
|
||||
fail-fast: false
|
||||
steps:
|
||||
|
@ -41,12 +41,12 @@ jobs:
|
|||
- name: Change config filename
|
||||
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||
|
||||
- name: Change config filename for macOS
|
||||
run: sed -r -i '' 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os == 'macOS-latest'
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os == 'macos-13'
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER
|
||||
|
@ -61,15 +61,15 @@ jobs:
|
|||
|
||||
- name: Publish Ryujinx
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||
|
||||
- name: Publish Ryujinx.Headless.SDL2
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||
|
||||
- name: Publish Ryujinx.Gtk3
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_gtk -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Gtk3 --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||
|
||||
- name: Set executable bit
|
||||
run: |
|
||||
|
@ -83,21 +83,21 @@ jobs:
|
|||
with:
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||
path: publish
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||
|
||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||
path: publish_sdl2_headless
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||
|
||||
- name: Upload Ryujinx.Gtk3 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gtk-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||
path: publish_gtk
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||
|
||||
build_macos:
|
||||
name: macOS Universal (${{ matrix.configuration }})
|
||||
|
|
|
@ -8,19 +8,19 @@
|
|||
<PackageVersion Include="Avalonia.Desktop" Version="11.0.10" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.10" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.10" />
|
||||
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.14" />
|
||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.14" />
|
||||
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.18" />
|
||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.18" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageVersion Include="DynamicData" Version="8.3.27" />
|
||||
<PackageVersion Include="DynamicData" Version="8.4.1" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.3.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.5.1" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
|
@ -42,7 +42,7 @@
|
|||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.7" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.8" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
||||
|
|
|
@ -36,8 +36,8 @@
|
|||
|
||||
## Compatibility
|
||||
|
||||
As of October 2023, Ryujinx has been tested on approximately 4,200 titles;
|
||||
over 4,150 boot past menus and into gameplay, with roughly 3,500 of those being considered playable.
|
||||
As of May 2024, Ryujinx has been tested on approximately 4,300 titles;
|
||||
over 4,100 boot past menus and into gameplay, with roughly 3,550 of those being considered playable.
|
||||
|
||||
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues).
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a0b4bc4d_002Dd13b_002D4a37_002Db37e_002Dc9c6864e4302/@EntryIndexedValue"><Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy></Policy></s:String>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ASET/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Astc/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Luma/@EntryIndexedValue">True</s:Boolean>
|
||||
|
|
|
@ -33,8 +33,3 @@ Project Docs
|
|||
=================
|
||||
|
||||
To be added. Many project files will contain basic XML docs for key functions and classes in the meantime.
|
||||
|
||||
Other Information
|
||||
=================
|
||||
|
||||
- N/A
|
||||
|
|
|
@ -19,6 +19,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))
|
||||
|
@ -467,6 +473,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))
|
||||
|
|
|
@ -157,7 +157,7 @@ namespace ARMeilleure.Instructions
|
|||
|
||||
context.Copy(temp, value);
|
||||
|
||||
if (!context.Memory.Type.IsHostMapped())
|
||||
if (!context.Memory.Type.IsHostMappedOrTracked())
|
||||
{
|
||||
context.Branch(lblEnd);
|
||||
|
||||
|
@ -198,7 +198,7 @@ namespace ARMeilleure.Instructions
|
|||
|
||||
SetInt(context, rt, value);
|
||||
|
||||
if (!context.Memory.Type.IsHostMapped())
|
||||
if (!context.Memory.Type.IsHostMappedOrTracked())
|
||||
{
|
||||
context.Branch(lblEnd);
|
||||
|
||||
|
@ -265,7 +265,7 @@ namespace ARMeilleure.Instructions
|
|||
|
||||
context.Copy(GetVec(rt), value);
|
||||
|
||||
if (!context.Memory.Type.IsHostMapped())
|
||||
if (!context.Memory.Type.IsHostMappedOrTracked())
|
||||
{
|
||||
context.Branch(lblEnd);
|
||||
|
||||
|
@ -312,7 +312,7 @@ namespace ARMeilleure.Instructions
|
|||
break;
|
||||
}
|
||||
|
||||
if (!context.Memory.Type.IsHostMapped())
|
||||
if (!context.Memory.Type.IsHostMappedOrTracked())
|
||||
{
|
||||
context.Branch(lblEnd);
|
||||
|
||||
|
@ -385,7 +385,7 @@ namespace ARMeilleure.Instructions
|
|||
break;
|
||||
}
|
||||
|
||||
if (!context.Memory.Type.IsHostMapped())
|
||||
if (!context.Memory.Type.IsHostMappedOrTracked())
|
||||
{
|
||||
context.Branch(lblEnd);
|
||||
|
||||
|
@ -403,6 +403,27 @@ namespace ARMeilleure.Instructions
|
|||
{
|
||||
return EmitHostMappedPointer(context, address);
|
||||
}
|
||||
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));
|
||||
|
||||
return context.Add(address, context.Load(OperandType.I64, context.Add(ptBase, context.ShiftLeft(ptOffset, Const(3)))));
|
||||
}
|
||||
|
||||
int ptLevelBits = context.Memory.AddressSpaceBits - PageBits;
|
||||
int ptLevelSize = 1 << ptLevelBits;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -29,6 +29,18 @@ 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,
|
||||
}
|
||||
|
||||
public static class MemoryManagerTypeExtensions
|
||||
|
@ -37,5 +49,15 @@ namespace ARMeilleure.Memory
|
|||
{
|
||||
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.IsHostMapped() || type.IsHostTracked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,10 +21,8 @@ namespace ARMeilleure.Signal
|
|||
|
||||
private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005;
|
||||
|
||||
private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr signalStructPtr, Operand faultAddress, Operand isWrite, int rangeStructSize, ulong pageSize)
|
||||
private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr signalStructPtr, Operand faultAddress, Operand isWrite, int rangeStructSize)
|
||||
{
|
||||
ulong pageMask = pageSize - 1;
|
||||
|
||||
Operand inRegionLocal = context.AllocateLocal(OperandType.I32);
|
||||
context.Copy(inRegionLocal, Const(0));
|
||||
|
||||
|
@ -51,7 +49,7 @@ namespace ARMeilleure.Signal
|
|||
// 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));
|
||||
|
@ -62,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);
|
||||
|
||||
|
@ -155,7 +155,7 @@ namespace ARMeilleure.Signal
|
|||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
public static byte[] GenerateUnixSignalHandler(IntPtr signalStructPtr, int rangeStructSize, ulong pageSize)
|
||||
public static byte[] GenerateUnixSignalHandler(IntPtr signalStructPtr, int rangeStructSize)
|
||||
{
|
||||
EmitterContext context = new();
|
||||
|
||||
|
@ -168,7 +168,7 @@ namespace ARMeilleure.Signal
|
|||
|
||||
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
|
||||
|
||||
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize, pageSize);
|
||||
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize);
|
||||
|
||||
Operand endLabel = Label();
|
||||
|
||||
|
@ -203,7 +203,7 @@ namespace ARMeilleure.Signal
|
|||
return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Code;
|
||||
}
|
||||
|
||||
public static byte[] GenerateWindowsSignalHandler(IntPtr signalStructPtr, int rangeStructSize, ulong pageSize)
|
||||
public static byte[] GenerateWindowsSignalHandler(IntPtr signalStructPtr, int rangeStructSize)
|
||||
{
|
||||
EmitterContext context = new();
|
||||
|
||||
|
@ -232,7 +232,7 @@ namespace ARMeilleure.Signal
|
|||
|
||||
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
|
||||
|
||||
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize, pageSize);
|
||||
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize);
|
||||
|
||||
Operand endLabel = Label();
|
||||
|
||||
|
@ -256,5 +256,86 @@ namespace ARMeilleure.Signal
|
|||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,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 = 6634; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
|
@ -857,8 +857,14 @@ namespace ARMeilleure.Translation.PTC
|
|||
|
||||
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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -87,7 +89,9 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||
return;
|
||||
}
|
||||
|
||||
byte[] samples = new byte[frameCount * _bytesPerFrame];
|
||||
using IMemoryOwner<byte> samplesOwner = ByteMemoryPool.Rent(frameCount * _bytesPerFrame);
|
||||
|
||||
Span<byte> samples = samplesOwner.Memory.Span;
|
||||
|
||||
_ringBuffer.Read(samples, 0, samples.Length);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -37,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();
|
||||
}
|
||||
|
@ -120,7 +122,9 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
|||
|
||||
int channelCount = areas.Length;
|
||||
|
||||
byte[] samples = new byte[frameCount * bytesPerFrame];
|
||||
using IMemoryOwner<byte> samplesOwner = ByteMemoryPool.Rent(frameCount * bytesPerFrame);
|
||||
|
||||
Span<byte> samples = samplesOwner.Memory.Span;
|
||||
|
||||
_ringBuffer.Read(samples, 0, samples.Length);
|
||||
|
||||
|
|
|
@ -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 IMemoryOwner<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 = ByteMemoryPool.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];
|
||||
IMemoryOwner<byte> newBufferOwner = ByteMemoryPool.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)..]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
|||
/// <summary>
|
||||
/// Output information for behaviour.
|
||||
/// </summary>
|
||||
/// <remarks>This is used to report errors to the user during <see cref="Server.AudioRenderSystem.Update(Memory{byte}, Memory{byte}, ReadOnlyMemory{byte})"/> processing.</remarks>
|
||||
/// <remarks>This is used to report errors to the user during <see cref="Server.AudioRenderSystem.Update(Memory{byte}, Memory{byte}, System.Buffers.ReadOnlySequence{byte})"/> processing.</remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct BehaviourErrorInfoOutStatus
|
||||
{
|
||||
|
|
|
@ -386,7 +386,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
}
|
||||
|
||||
public ResultCode Update(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input)
|
||||
public ResultCode Update(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlySequence<byte> input)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
|
@ -419,14 +419,16 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateVoices(_voiceContext, _memoryPools);
|
||||
PoolMapper poolMapper = new PoolMapper(_processHandle, _memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
|
||||
result = stateUpdater.UpdateVoices(_voiceContext, poolMapper);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateEffects(_effectContext, _isActive, _memoryPools);
|
||||
result = stateUpdater.UpdateEffects(_effectContext, _isActive, poolMapper);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
|
@ -450,7 +452,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return result;
|
||||
}
|
||||
|
||||
result = stateUpdater.UpdateSinks(_sinkContext, _memoryPools);
|
||||
result = stateUpdater.UpdateSinks(_sinkContext, poolMapper);
|
||||
|
||||
if (result != ResultCode.Success)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
||||
|
||||
|
@ -211,7 +212,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
/// <summary>
|
||||
/// Check if the audio renderer should fix the GC-ADPCM context not being provided to the DSP.
|
||||
/// </summary>
|
||||
/// <returns>True if if the audio renderer should fix it.</returns>
|
||||
/// <returns>True if the audio renderer should fix it.</returns>
|
||||
public bool IsAdpcmLoopContextBugFixed()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2);
|
||||
|
@ -273,7 +274,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer should trust the user destination count in <see cref="Splitter.SplitterState.Update(Splitter.SplitterContext, ref Parameter.SplitterInParameter, ReadOnlySpan{byte})"/>.
|
||||
/// Check if the audio renderer should trust the user destination count in <see cref="Renderer.Server.Splitter.SplitterState.Update(Renderer.Server.Splitter.SplitterContext, Renderer.Parameter.SplitterInParameter, SequenceReader{byte})"/>.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer should trust the user destination count.</returns>
|
||||
public bool IsSplitterBugFixed()
|
||||
|
|
|
@ -33,21 +33,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
return WorkBuffers[index].GetReference(true);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
Parameter = MemoryMarshal.Cast<byte, AuxiliaryBufferParameter>(parameter.SpecificData)[0];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
|
|
@ -81,7 +81,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <returns>Returns true if the <see cref="EffectType"/> sent by the user matches the internal <see cref="EffectType"/>.</returns>
|
||||
public bool IsTypeValid<T>(ref T parameter) where T : unmanaged, IEffectInParameter
|
||||
public bool IsTypeValid<T>(in T parameter) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
return parameter.Type == TargetEffectType;
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
/// Update the internal common parameters from a user parameter.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
protected void UpdateParameterBase<T>(ref T parameter) where T : unmanaged, IEffectInParameter
|
||||
protected void UpdateParameterBase<T>(in T parameter) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
MixId = parameter.MixId;
|
||||
ProcessingOrder = parameter.ProcessingOrder;
|
||||
|
@ -139,7 +139,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
/// <summary>
|
||||
/// Initialize the given <paramref name="state"/> result state.
|
||||
/// </summary>
|
||||
/// <param name="state">The state to initalize</param>
|
||||
/// <param name="state">The state to initialize</param>
|
||||
public virtual void InitializeResultState(ref EffectResultState state) { }
|
||||
|
||||
/// <summary>
|
||||
|
@ -155,9 +155,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
/// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="mapper">The mapper to use.</param>
|
||||
public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public virtual void Update(out ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
updateErrorInfo = new ErrorInfo();
|
||||
}
|
||||
|
@ -168,9 +168,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
/// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="mapper">The mapper to use.</param>
|
||||
public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public virtual void Update(out ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
updateErrorInfo = new ErrorInfo();
|
||||
}
|
||||
|
|
|
@ -35,21 +35,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
|
||||
public override EffectType TargetEffectType => EffectType.BiquadFilter;
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
Parameter = MemoryMarshal.Cast<byte, BiquadFilterEffectParameter>(parameter.SpecificData)[0];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
|
|
@ -19,21 +19,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
|
||||
public override EffectType TargetEffectType => EffectType.BufferMix;
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
Parameter = MemoryMarshal.Cast<byte, BufferMixParameter>(parameter.SpecificData)[0];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
|
|
@ -32,21 +32,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
return WorkBuffers[index].GetReference(true);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
Parameter = MemoryMarshal.Cast<byte, AuxiliaryBufferParameter>(parameter.SpecificData)[0];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
|
|
@ -39,17 +39,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
return GetSingleBuffer();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
// Nintendo doesn't do anything here but we still require updateErrorInfo to be initialised.
|
||||
updateErrorInfo = new BehaviourParameter.ErrorInfo();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
Parameter = MemoryMarshal.Cast<byte, CompressorParameter>(parameter.SpecificData)[0];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
|
|
@ -37,19 +37,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
return GetSingleBuffer();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
ref DelayParameter delayParameter = ref MemoryMarshal.Cast<byte, DelayParameter>(parameter.SpecificData)[0];
|
||||
|
||||
|
@ -57,7 +57,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
|
||||
if (delayParameter.IsChannelCountMaxValid())
|
||||
{
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
UsageState oldParameterStatus = Parameter.Status;
|
||||
|
||||
|
|
|
@ -39,25 +39,25 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
return GetSingleBuffer();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
ref LimiterParameter limiterParameter = ref MemoryMarshal.Cast<byte, LimiterParameter>(parameter.SpecificData)[0];
|
||||
|
||||
updateErrorInfo = new BehaviourParameter.ErrorInfo();
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
Parameter = limiterParameter;
|
||||
|
||||
|
|
|
@ -36,19 +36,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
return GetSingleBuffer();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
ref Reverb3dParameter reverbParameter = ref MemoryMarshal.Cast<byte, Reverb3dParameter>(parameter.SpecificData)[0];
|
||||
|
||||
|
@ -56,7 +56,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
|
||||
if (reverbParameter.IsChannelCountMaxValid())
|
||||
{
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
UsageState oldParameterStatus = Parameter.ParameterStatus;
|
||||
|
||||
|
|
|
@ -39,19 +39,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
return GetSingleBuffer();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Update(out updateErrorInfo, ref parameter, mapper);
|
||||
Update(out updateErrorInfo, in parameter, mapper);
|
||||
}
|
||||
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
ref ReverbParameter reverbParameter = ref MemoryMarshal.Cast<byte, ReverbParameter>(parameter.SpecificData)[0];
|
||||
|
||||
|
@ -59,7 +59,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
|||
|
||||
if (reverbParameter.IsChannelCountMaxValid())
|
||||
{
|
||||
UpdateParameterBase(ref parameter);
|
||||
UpdateParameterBase(in parameter);
|
||||
|
||||
UsageState oldParameterStatus = Parameter.Status;
|
||||
|
||||
|
|
|
@ -249,7 +249,7 @@ namespace Ryujinx.Audio.Renderer.Server.MemoryPool
|
|||
/// <param name="inParameter">Input user parameter.</param>
|
||||
/// <param name="outStatus">Output user parameter.</param>
|
||||
/// <returns>Returns the <see cref="UpdateResult"/> of the operations performed.</returns>
|
||||
public UpdateResult Update(ref MemoryPoolState memoryPool, ref MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus)
|
||||
public UpdateResult Update(ref MemoryPoolState memoryPool, in MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus)
|
||||
{
|
||||
MemoryPoolUserState inputState = inParameter.State;
|
||||
|
||||
|
|
|
@ -195,7 +195,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
|
|||
/// <param name="parameter">The input parameter of the mix.</param>
|
||||
/// <param name="splitterContext">The splitter context.</param>
|
||||
/// <returns>Return true, new connections were done on the adjacency matrix.</returns>
|
||||
private bool UpdateConnection(EdgeMatrix edgeMatrix, ref MixParameter parameter, ref SplitterContext splitterContext)
|
||||
private bool UpdateConnection(EdgeMatrix edgeMatrix, in MixParameter parameter, ref SplitterContext splitterContext)
|
||||
{
|
||||
bool hasNewConnections;
|
||||
|
||||
|
@ -259,7 +259,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
|
|||
/// <param name="splitterContext">The splitter context.</param>
|
||||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
/// <returns>Return true if the mix was changed.</returns>
|
||||
public bool Update(EdgeMatrix edgeMatrix, ref MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext)
|
||||
public bool Update(EdgeMatrix edgeMatrix, in MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext)
|
||||
{
|
||||
bool isDirty;
|
||||
|
||||
|
@ -273,7 +273,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
|
|||
|
||||
if (behaviourContext.IsSplitterSupported())
|
||||
{
|
||||
isDirty = UpdateConnection(edgeMatrix, ref parameter, ref splitterContext);
|
||||
isDirty = UpdateConnection(edgeMatrix, in parameter, ref splitterContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -59,7 +59,7 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
|
|||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <returns>Return true, if the <see cref="SinkType"/> sent by the user match the internal <see cref="SinkType"/>.</returns>
|
||||
public bool IsTypeValid(ref SinkInParameter parameter)
|
||||
public bool IsTypeValid(in SinkInParameter parameter)
|
||||
{
|
||||
return parameter.Type == TargetSinkType;
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
|
|||
/// Update the internal common parameters from user parameter.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
protected void UpdateStandardParameter(ref SinkInParameter parameter)
|
||||
protected void UpdateStandardParameter(in SinkInParameter parameter)
|
||||
{
|
||||
if (IsUsed != parameter.IsUsed)
|
||||
{
|
||||
|
@ -92,9 +92,9 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
|
|||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="outStatus">The user output status.</param>
|
||||
/// <param name="mapper">The mapper to use.</param>
|
||||
public virtual void Update(out ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
|
||||
public virtual void Update(out ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
errorInfo = new ErrorInfo();
|
||||
}
|
||||
|
|
|
@ -44,18 +44,18 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
|
|||
|
||||
public override SinkType TargetSinkType => SinkType.CircularBuffer;
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
|
||||
{
|
||||
errorInfo = new BehaviourParameter.ErrorInfo();
|
||||
outStatus = new SinkOutStatus();
|
||||
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
ref CircularBufferParameter inputDeviceParameter = ref MemoryMarshal.Cast<byte, CircularBufferParameter>(parameter.SpecificData)[0];
|
||||
|
||||
if (parameter.IsUsed != IsUsed || ShouldSkip)
|
||||
{
|
||||
UpdateStandardParameter(ref parameter);
|
||||
UpdateStandardParameter(in parameter);
|
||||
|
||||
if (parameter.IsUsed)
|
||||
{
|
||||
|
|
|
@ -49,15 +49,15 @@ namespace Ryujinx.Audio.Renderer.Server.Sink
|
|||
|
||||
public override SinkType TargetSinkType => SinkType.Device;
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
|
||||
public override void Update(out BehaviourParameter.ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
Debug.Assert(IsTypeValid(in parameter));
|
||||
|
||||
ref DeviceParameter inputDeviceParameter = ref MemoryMarshal.Cast<byte, DeviceParameter>(parameter.SpecificData)[0];
|
||||
|
||||
if (parameter.IsUsed != IsUsed)
|
||||
{
|
||||
UpdateStandardParameter(ref parameter);
|
||||
UpdateStandardParameter(in parameter);
|
||||
Parameter = inputDeviceParameter;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -2,10 +2,11 @@ using Ryujinx.Audio.Renderer.Common;
|
|||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Extensions;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||
{
|
||||
|
@ -25,7 +26,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
private Memory<SplitterDestination> _splitterDestinations;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, ref SplitterInParameter, ReadOnlySpan{byte})"/>.
|
||||
/// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.
|
||||
/// </summary>
|
||||
public bool IsBugFixed { get; private set; }
|
||||
|
||||
|
@ -110,7 +111,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// </summary>
|
||||
/// <param name="splitters">The <see cref="SplitterState"/> storage.</param>
|
||||
/// <param name="splitterDestinations">The <see cref="SplitterDestination"/> storage.</param>
|
||||
/// <param name="isBugFixed">If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, ref SplitterInParameter, ReadOnlySpan{byte})"/>.</param>
|
||||
/// <param name="isBugFixed">If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.</param>
|
||||
private void Setup(Memory<SplitterState> splitters, Memory<SplitterDestination> splitterDestinations, bool isBugFixed)
|
||||
{
|
||||
_splitters = splitters;
|
||||
|
@ -148,11 +149,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// </summary>
|
||||
/// <param name="inputHeader">The splitter header.</param>
|
||||
/// <param name="input">The raw data after the splitter header.</param>
|
||||
private void UpdateState(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan<byte> input)
|
||||
private void UpdateState(in SplitterInParameterHeader inputHeader, ref SequenceReader<byte> input)
|
||||
{
|
||||
for (int i = 0; i < inputHeader.SplitterCount; i++)
|
||||
{
|
||||
SplitterInParameter parameter = MemoryMarshal.Read<SplitterInParameter>(input);
|
||||
ref readonly SplitterInParameter parameter = ref input.GetRefOrRefToCopy<SplitterInParameter>(out _);
|
||||
|
||||
Debug.Assert(parameter.IsMagicValid());
|
||||
|
||||
|
@ -162,10 +163,16 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
{
|
||||
ref SplitterState splitter = ref GetState(parameter.Id);
|
||||
|
||||
splitter.Update(this, ref parameter, input[Unsafe.SizeOf<SplitterInParameter>()..]);
|
||||
splitter.Update(this, in parameter, ref input);
|
||||
}
|
||||
|
||||
input = input[(0x1C + parameter.DestinationCount * 4)..];
|
||||
// NOTE: there are 12 bytes of unused/unknown data after the destination IDs array.
|
||||
input.Advance(0xC);
|
||||
}
|
||||
else
|
||||
{
|
||||
input.Rewind(Unsafe.SizeOf<SplitterInParameter>());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,11 +182,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// </summary>
|
||||
/// <param name="inputHeader">The splitter header.</param>
|
||||
/// <param name="input">The raw data after the splitter header.</param>
|
||||
private void UpdateData(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan<byte> input)
|
||||
private void UpdateData(in SplitterInParameterHeader inputHeader, ref SequenceReader<byte> input)
|
||||
{
|
||||
for (int i = 0; i < inputHeader.SplitterDestinationCount; i++)
|
||||
{
|
||||
SplitterDestinationInParameter parameter = MemoryMarshal.Read<SplitterDestinationInParameter>(input);
|
||||
ref readonly SplitterDestinationInParameter parameter = ref input.GetRefOrRefToCopy<SplitterDestinationInParameter>(out _);
|
||||
|
||||
Debug.Assert(parameter.IsMagicValid());
|
||||
|
||||
|
@ -191,8 +198,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
|
||||
destination.Update(parameter);
|
||||
}
|
||||
|
||||
input = input[Unsafe.SizeOf<SplitterDestinationInParameter>()..];
|
||||
}
|
||||
else
|
||||
{
|
||||
input.Rewind(Unsafe.SizeOf<SplitterDestinationInParameter>());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -201,36 +211,33 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// Update splitter from user parameters.
|
||||
/// </summary>
|
||||
/// <param name="input">The input raw user data.</param>
|
||||
/// <param name="consumedSize">The total consumed size.</param>
|
||||
/// <returns>Return true if the update was successful.</returns>
|
||||
public bool Update(ReadOnlySpan<byte> input, out int consumedSize)
|
||||
public bool Update(ref SequenceReader<byte> input)
|
||||
{
|
||||
if (_splitterDestinations.IsEmpty || _splitters.IsEmpty)
|
||||
{
|
||||
consumedSize = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int originalSize = input.Length;
|
||||
|
||||
SplitterInParameterHeader header = SpanIOHelper.Read<SplitterInParameterHeader>(ref input);
|
||||
ref readonly SplitterInParameterHeader header = ref input.GetRefOrRefToCopy<SplitterInParameterHeader>(out _);
|
||||
|
||||
if (header.IsMagicValid())
|
||||
{
|
||||
ClearAllNewConnectionFlag();
|
||||
|
||||
UpdateState(ref header, ref input);
|
||||
UpdateData(ref header, ref input);
|
||||
UpdateState(in header, ref input);
|
||||
UpdateData(in header, ref input);
|
||||
|
||||
consumedSize = BitUtils.AlignUp(originalSize - input.Length, 0x10);
|
||||
input.SetConsumed(BitUtils.AlignUp(input.Consumed, 0x10));
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
input.Rewind(Unsafe.SizeOf<SplitterInParameterHeader>());
|
||||
|
||||
consumedSize = 0;
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Common.Extensions;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
|
@ -122,7 +123,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// <param name="context">The splitter context.</param>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="input">The raw input data after the <paramref name="parameter"/>.</param>
|
||||
public void Update(SplitterContext context, ref SplitterInParameter parameter, ReadOnlySpan<byte> input)
|
||||
public void Update(SplitterContext context, in SplitterInParameter parameter, ref SequenceReader<byte> input)
|
||||
{
|
||||
ClearLinks();
|
||||
|
||||
|
@ -139,9 +140,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
|
||||
if (destinationCount > 0)
|
||||
{
|
||||
ReadOnlySpan<int> destinationIds = MemoryMarshal.Cast<byte, int>(input);
|
||||
input.ReadLittleEndian(out int destinationId);
|
||||
|
||||
Memory<SplitterDestination> destination = context.GetDestinationMemory(destinationIds[0]);
|
||||
Memory<SplitterDestination> destination = context.GetDestinationMemory(destinationId);
|
||||
|
||||
SetDestination(ref destination.Span[0]);
|
||||
|
||||
|
@ -149,13 +150,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
|
||||
for (int i = 1; i < destinationCount; i++)
|
||||
{
|
||||
Memory<SplitterDestination> nextDestination = context.GetDestinationMemory(destinationIds[i]);
|
||||
input.ReadLittleEndian(out destinationId);
|
||||
|
||||
Memory<SplitterDestination> nextDestination = context.GetDestinationMemory(destinationId);
|
||||
|
||||
destination.Span[0].Link(ref nextDestination.Span[0]);
|
||||
destination = nextDestination;
|
||||
}
|
||||
}
|
||||
|
||||
if (destinationCount < parameter.DestinationCount)
|
||||
{
|
||||
input.Advance((parameter.DestinationCount - destinationCount) * sizeof(int));
|
||||
}
|
||||
|
||||
Debug.Assert(parameter.Id == Id);
|
||||
|
||||
if (parameter.Id == Id)
|
||||
|
|
|
@ -9,41 +9,40 @@ using Ryujinx.Audio.Renderer.Server.Sink;
|
|||
using Ryujinx.Audio.Renderer.Server.Splitter;
|
||||
using Ryujinx.Audio.Renderer.Server.Voice;
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using Ryujinx.Common.Extensions;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
public class StateUpdater
|
||||
public ref struct StateUpdater
|
||||
{
|
||||
private readonly ReadOnlyMemory<byte> _inputOrigin;
|
||||
private SequenceReader<byte> _inputReader;
|
||||
|
||||
private readonly ReadOnlyMemory<byte> _outputOrigin;
|
||||
private ReadOnlyMemory<byte> _input;
|
||||
|
||||
private Memory<byte> _output;
|
||||
private readonly uint _processHandle;
|
||||
private BehaviourContext _behaviourContext;
|
||||
|
||||
private UpdateDataHeader _inputHeader;
|
||||
private readonly ref readonly UpdateDataHeader _inputHeader;
|
||||
private readonly Memory<UpdateDataHeader> _outputHeader;
|
||||
|
||||
private ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0];
|
||||
private readonly ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0];
|
||||
|
||||
public StateUpdater(ReadOnlyMemory<byte> input, Memory<byte> output, uint processHandle, BehaviourContext behaviourContext)
|
||||
public StateUpdater(ReadOnlySequence<byte> input, Memory<byte> output, uint processHandle, BehaviourContext behaviourContext)
|
||||
{
|
||||
_input = input;
|
||||
_inputOrigin = _input;
|
||||
_inputReader = new SequenceReader<byte>(input);
|
||||
_output = output;
|
||||
_outputOrigin = _output;
|
||||
_processHandle = processHandle;
|
||||
_behaviourContext = behaviourContext;
|
||||
|
||||
_inputHeader = SpanIOHelper.Read<UpdateDataHeader>(ref _input);
|
||||
_inputHeader = ref _inputReader.GetRefOrRefToCopy<UpdateDataHeader>(out _);
|
||||
|
||||
_outputHeader = SpanMemoryManager<UpdateDataHeader>.Cast(_output[..Unsafe.SizeOf<UpdateDataHeader>()]);
|
||||
OutputHeader.Initialize(_behaviourContext.UserRevision);
|
||||
|
@ -52,7 +51,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
public ResultCode UpdateBehaviourContext()
|
||||
{
|
||||
BehaviourParameter parameter = SpanIOHelper.Read<BehaviourParameter>(ref _input);
|
||||
ref readonly BehaviourParameter parameter = ref _inputReader.GetRefOrRefToCopy<BehaviourParameter>(out _);
|
||||
|
||||
if (!BehaviourContext.CheckValidRevision(parameter.UserRevision) || parameter.UserRevision != _behaviourContext.UserRevision)
|
||||
{
|
||||
|
@ -81,11 +80,11 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
foreach (ref MemoryPoolState memoryPool in memoryPools)
|
||||
{
|
||||
MemoryPoolInParameter parameter = SpanIOHelper.Read<MemoryPoolInParameter>(ref _input);
|
||||
ref readonly MemoryPoolInParameter parameter = ref _inputReader.GetRefOrRefToCopy<MemoryPoolInParameter>(out _);
|
||||
|
||||
ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef<MemoryPoolOutStatus>(ref _output)[0];
|
||||
|
||||
PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, ref parameter, ref outStatus);
|
||||
PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, in parameter, ref outStatus);
|
||||
|
||||
if (updateResult != PoolMapper.UpdateResult.Success &&
|
||||
updateResult != PoolMapper.UpdateResult.MapError &&
|
||||
|
@ -115,7 +114,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
VoiceChannelResourceInParameter parameter = SpanIOHelper.Read<VoiceChannelResourceInParameter>(ref _input);
|
||||
ref readonly VoiceChannelResourceInParameter parameter = ref _inputReader.GetRefOrRefToCopy<VoiceChannelResourceInParameter>(out _);
|
||||
|
||||
ref VoiceChannelResource resource = ref context.GetChannelResource(i);
|
||||
|
||||
|
@ -127,7 +126,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateVoices(VoiceContext context, Memory<MemoryPoolState> memoryPools)
|
||||
public ResultCode UpdateVoices(VoiceContext context, PoolMapper mapper)
|
||||
{
|
||||
if (context.GetCount() * Unsafe.SizeOf<VoiceInParameter>() != _inputHeader.VoicesSize)
|
||||
{
|
||||
|
@ -136,11 +135,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<VoiceInParameter> parameters = MemoryMarshal.Cast<byte, VoiceInParameter>(_input[..(int)_inputHeader.VoicesSize].Span);
|
||||
|
||||
_input = _input[(int)_inputHeader.VoicesSize..];
|
||||
|
||||
PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
// First make everything not in use.
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
|
@ -157,7 +152,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
// Start processing
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
VoiceInParameter parameter = parameters[i];
|
||||
ref readonly VoiceInParameter parameter = ref _inputReader.GetRefOrRefToCopy<VoiceInParameter>(out _);
|
||||
|
||||
voiceUpdateStates.Fill(Memory<VoiceUpdateState>.Empty);
|
||||
|
||||
|
@ -181,14 +176,14 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
currentVoiceState.Initialize();
|
||||
}
|
||||
|
||||
currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, ref parameter, ref mapper, ref _behaviourContext);
|
||||
currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, in parameter, mapper, ref _behaviourContext);
|
||||
|
||||
if (updateParameterError.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
_behaviourContext.AppendError(ref updateParameterError);
|
||||
}
|
||||
|
||||
currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, ref parameter, voiceUpdateStates, ref mapper, ref _behaviourContext);
|
||||
currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, in parameter, voiceUpdateStates, mapper, ref _behaviourContext);
|
||||
|
||||
foreach (ref ErrorInfo errorInfo in waveBufferUpdateErrorInfos.AsSpan())
|
||||
{
|
||||
|
@ -198,7 +193,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
}
|
||||
|
||||
currentVoiceState.WriteOutStatus(ref outStatus, ref parameter, voiceUpdateStates);
|
||||
currentVoiceState.WriteOutStatus(ref outStatus, in parameter, voiceUpdateStates);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,10 +206,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize);
|
||||
|
||||
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.VoicesSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private static void ResetEffect<T>(ref BaseEffect effect, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
private static void ResetEffect<T>(ref BaseEffect effect, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
effect.ForceUnmapBuffers(mapper);
|
||||
|
||||
|
@ -234,17 +231,17 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
};
|
||||
}
|
||||
|
||||
public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
|
||||
public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, PoolMapper mapper)
|
||||
{
|
||||
if (_behaviourContext.IsEffectInfoVersion2Supported())
|
||||
{
|
||||
return UpdateEffectsVersion2(context, isAudioRendererActive, memoryPools);
|
||||
return UpdateEffectsVersion2(context, isAudioRendererActive, mapper);
|
||||
}
|
||||
|
||||
return UpdateEffectsVersion1(context, isAudioRendererActive, memoryPools);
|
||||
return UpdateEffectsVersion1(context, isAudioRendererActive, mapper);
|
||||
}
|
||||
|
||||
public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
|
||||
public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, PoolMapper mapper)
|
||||
{
|
||||
if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion2>() != _inputHeader.EffectsSize)
|
||||
{
|
||||
|
@ -253,26 +250,22 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<EffectInParameterVersion2> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion2>(_input[..(int)_inputHeader.EffectsSize].Span);
|
||||
|
||||
_input = _input[(int)_inputHeader.EffectsSize..];
|
||||
|
||||
PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
EffectInParameterVersion2 parameter = parameters[i];
|
||||
ref readonly EffectInParameterVersion2 parameter = ref _inputReader.GetRefOrRefToCopy<EffectInParameterVersion2>(out _);
|
||||
|
||||
ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion2>(ref _output)[0];
|
||||
|
||||
ref BaseEffect effect = ref context.GetEffect(i);
|
||||
|
||||
if (!effect.IsTypeValid(ref parameter))
|
||||
if (!effect.IsTypeValid(in parameter))
|
||||
{
|
||||
ResetEffect(ref effect, ref parameter, mapper);
|
||||
ResetEffect(ref effect, in parameter, mapper);
|
||||
}
|
||||
|
||||
effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper);
|
||||
effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper);
|
||||
|
||||
if (updateErrorInfo.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
|
@ -297,10 +290,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
|
||||
|
||||
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
|
||||
public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, PoolMapper mapper)
|
||||
{
|
||||
if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion1>() != _inputHeader.EffectsSize)
|
||||
{
|
||||
|
@ -309,26 +304,22 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<EffectInParameterVersion1> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion1>(_input[..(int)_inputHeader.EffectsSize].Span);
|
||||
|
||||
_input = _input[(int)_inputHeader.EffectsSize..];
|
||||
|
||||
PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
EffectInParameterVersion1 parameter = parameters[i];
|
||||
ref readonly EffectInParameterVersion1 parameter = ref _inputReader.GetRefOrRefToCopy<EffectInParameterVersion1>(out _);
|
||||
|
||||
ref EffectOutStatusVersion1 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion1>(ref _output)[0];
|
||||
|
||||
ref BaseEffect effect = ref context.GetEffect(i);
|
||||
|
||||
if (!effect.IsTypeValid(ref parameter))
|
||||
if (!effect.IsTypeValid(in parameter))
|
||||
{
|
||||
ResetEffect(ref effect, ref parameter, mapper);
|
||||
ResetEffect(ref effect, in parameter, mapper);
|
||||
}
|
||||
|
||||
effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper);
|
||||
effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper);
|
||||
|
||||
if (updateErrorInfo.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
|
@ -345,38 +336,40 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
|
||||
|
||||
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateSplitter(SplitterContext context)
|
||||
{
|
||||
if (context.Update(_input.Span, out int consumedSize))
|
||||
if (context.Update(ref _inputReader))
|
||||
{
|
||||
_input = _input[consumedSize..];
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
private static bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, ReadOnlySpan<MixParameter> parameters)
|
||||
private static bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, SequenceReader<byte> parameters)
|
||||
{
|
||||
uint maxMixStateCount = mixContext.GetCount();
|
||||
uint totalRequiredMixBufferCount = 0;
|
||||
|
||||
for (int i = 0; i < inputMixCount; i++)
|
||||
{
|
||||
if (parameters[i].IsUsed)
|
||||
ref readonly MixParameter parameter = ref parameters.GetRefOrRefToCopy<MixParameter>(out _);
|
||||
|
||||
if (parameter.IsUsed)
|
||||
{
|
||||
if (parameters[i].DestinationMixId != Constants.UnusedMixId &&
|
||||
parameters[i].DestinationMixId > maxMixStateCount &&
|
||||
parameters[i].MixId != Constants.FinalMixId)
|
||||
if (parameter.DestinationMixId != Constants.UnusedMixId &&
|
||||
parameter.DestinationMixId > maxMixStateCount &&
|
||||
parameter.MixId != Constants.FinalMixId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
totalRequiredMixBufferCount += parameters[i].BufferCount;
|
||||
totalRequiredMixBufferCount += parameter.BufferCount;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -391,7 +384,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
|
||||
{
|
||||
MixInParameterDirtyOnlyUpdate parameter = MemoryMarshal.Cast<byte, MixInParameterDirtyOnlyUpdate>(_input.Span)[0];
|
||||
ref readonly MixInParameterDirtyOnlyUpdate parameter = ref _inputReader.GetRefOrRefToCopy<MixInParameterDirtyOnlyUpdate>(out _);
|
||||
|
||||
mixCount = parameter.MixCount;
|
||||
|
||||
|
@ -411,25 +404,20 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
|
||||
{
|
||||
_input = _input[Unsafe.SizeOf<MixInParameterDirtyOnlyUpdate>()..];
|
||||
}
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
ReadOnlySpan<MixParameter> parameters = MemoryMarshal.Cast<byte, MixParameter>(_input.Span[..(int)inputMixSize]);
|
||||
int parameterCount = (int)inputMixSize / Unsafe.SizeOf<MixParameter>();
|
||||
|
||||
_input = _input[(int)inputMixSize..];
|
||||
|
||||
if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, parameters))
|
||||
if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, _inputReader))
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
bool isMixContextDirty = false;
|
||||
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
for (int i = 0; i < parameterCount; i++)
|
||||
{
|
||||
MixParameter parameter = parameters[i];
|
||||
ref readonly MixParameter parameter = ref _inputReader.GetRefOrRefToCopy<MixParameter>(out _);
|
||||
|
||||
int mixId = i;
|
||||
|
||||
|
@ -454,7 +442,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
if (mix.IsUsed)
|
||||
{
|
||||
isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, ref parameter, effectContext, splitterContext, _behaviourContext);
|
||||
isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, in parameter, effectContext, splitterContext, _behaviourContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -473,10 +461,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
}
|
||||
|
||||
_inputReader.SetConsumed(initialInputConsumed + inputMixSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private static void ResetSink(ref BaseSink sink, ref SinkInParameter parameter)
|
||||
private static void ResetSink(ref BaseSink sink, in SinkInParameter parameter)
|
||||
{
|
||||
sink.CleanUp();
|
||||
|
||||
|
@ -489,10 +479,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
};
|
||||
}
|
||||
|
||||
public ResultCode UpdateSinks(SinkContext context, Memory<MemoryPoolState> memoryPools)
|
||||
public ResultCode UpdateSinks(SinkContext context, PoolMapper mapper)
|
||||
{
|
||||
PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
|
||||
if (context.GetCount() * Unsafe.SizeOf<SinkInParameter>() != _inputHeader.SinksSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
|
@ -500,22 +488,20 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<SinkInParameter> parameters = MemoryMarshal.Cast<byte, SinkInParameter>(_input[..(int)_inputHeader.SinksSize].Span);
|
||||
|
||||
_input = _input[(int)_inputHeader.SinksSize..];
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
SinkInParameter parameter = parameters[i];
|
||||
ref readonly SinkInParameter parameter = ref _inputReader.GetRefOrRefToCopy<SinkInParameter>(out _);
|
||||
ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef<SinkOutStatus>(ref _output)[0];
|
||||
ref BaseSink sink = ref context.GetSink(i);
|
||||
|
||||
if (!sink.IsTypeValid(ref parameter))
|
||||
if (!sink.IsTypeValid(in parameter))
|
||||
{
|
||||
ResetSink(ref sink, ref parameter);
|
||||
ResetSink(ref sink, in parameter);
|
||||
}
|
||||
|
||||
sink.Update(out ErrorInfo updateErrorInfo, ref parameter, ref outStatus, mapper);
|
||||
sink.Update(out ErrorInfo updateErrorInfo, in parameter, ref outStatus, mapper);
|
||||
|
||||
if (updateErrorInfo.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
|
@ -530,6 +516,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize);
|
||||
|
||||
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.SinksSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
|
@ -540,7 +528,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
PerformanceInParameter parameter = SpanIOHelper.Read<PerformanceInParameter>(ref _input);
|
||||
ref readonly PerformanceInParameter parameter = ref _inputReader.GetRefOrRefToCopy<PerformanceInParameter>(out _);
|
||||
|
||||
ref PerformanceOutStatus outStatus = ref SpanIOHelper.GetWriteRef<PerformanceOutStatus>(ref _output)[0];
|
||||
|
||||
|
@ -585,9 +573,9 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode CheckConsumedSize()
|
||||
public readonly ResultCode CheckConsumedSize()
|
||||
{
|
||||
int consumedInputSize = _inputOrigin.Length - _input.Length;
|
||||
long consumedInputSize = _inputReader.Consumed;
|
||||
int consumedOutputSize = _outputOrigin.Length - _output.Length;
|
||||
|
||||
if (consumedInputSize != _inputHeader.TotalSize)
|
||||
|
|
|
@ -254,7 +254,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <returns>Return true, if the server voice information needs to be updated.</returns>
|
||||
private readonly bool ShouldUpdateParameters(ref VoiceInParameter parameter)
|
||||
private readonly bool ShouldUpdateParameters(in VoiceInParameter parameter)
|
||||
{
|
||||
if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress)
|
||||
{
|
||||
|
@ -273,7 +273,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="poolMapper">The mapper to use.</param>
|
||||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
public void UpdateParameters(out ErrorInfo outErrorInfo, ref VoiceInParameter parameter, ref PoolMapper poolMapper, ref BehaviourContext behaviourContext)
|
||||
public void UpdateParameters(out ErrorInfo outErrorInfo, in VoiceInParameter parameter, PoolMapper poolMapper, ref BehaviourContext behaviourContext)
|
||||
{
|
||||
InUse = parameter.InUse;
|
||||
Id = parameter.Id;
|
||||
|
@ -326,7 +326,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
VoiceDropFlag = false;
|
||||
}
|
||||
|
||||
if (ShouldUpdateParameters(ref parameter))
|
||||
if (ShouldUpdateParameters(in parameter))
|
||||
{
|
||||
DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize);
|
||||
}
|
||||
|
@ -380,7 +380,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
/// <param name="outStatus">The given user output.</param>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
|
||||
public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates)
|
||||
public void WriteOutStatus(ref VoiceOutStatus outStatus, in VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates)
|
||||
{
|
||||
#if DEBUG
|
||||
// Sanity check in debug mode of the internal state
|
||||
|
@ -426,7 +426,12 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
|
||||
/// <param name="mapper">The mapper to use.</param>
|
||||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext)
|
||||
public void UpdateWaveBuffers(
|
||||
out ErrorInfo[] errorInfos,
|
||||
in VoiceInParameter parameter,
|
||||
ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates,
|
||||
PoolMapper mapper,
|
||||
ref BehaviourContext behaviourContext)
|
||||
{
|
||||
errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2];
|
||||
|
||||
|
@ -444,7 +449,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
|
||||
for (int i = 0; i < Constants.VoiceWaveBufferCount; i++)
|
||||
{
|
||||
UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext);
|
||||
UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], mapper, ref behaviourContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -458,7 +463,14 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
|||
/// <param name="isValid">If set to true, the server side wavebuffer is considered valid.</param>
|
||||
/// <param name="mapper">The mapper to use.</param>
|
||||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
private void UpdateWaveBuffer(Span<ErrorInfo> errorInfos, ref WaveBuffer waveBuffer, ref WaveBufferInternal inputWaveBuffer, SampleFormat sampleFormat, bool isValid, ref PoolMapper mapper, ref BehaviourContext behaviourContext)
|
||||
private void UpdateWaveBuffer(
|
||||
Span<ErrorInfo> errorInfos,
|
||||
ref WaveBuffer waveBuffer,
|
||||
ref WaveBufferInternal inputWaveBuffer,
|
||||
SampleFormat sampleFormat,
|
||||
bool isValid,
|
||||
PoolMapper mapper,
|
||||
ref BehaviourContext behaviourContext)
|
||||
{
|
||||
if (!isValid && waveBuffer.IsSendToAudioProcessor && waveBuffer.BufferAddressInfo.CpuAddress != 0)
|
||||
{
|
||||
|
|
|
@ -5,10 +5,10 @@ namespace Ryujinx.Common.Collections
|
|||
/// </summary>
|
||||
public class IntrusiveRedBlackTreeNode<T> where T : IntrusiveRedBlackTreeNode<T>
|
||||
{
|
||||
internal bool Color = true;
|
||||
internal T Left;
|
||||
internal T Right;
|
||||
internal T Parent;
|
||||
public bool Color = true;
|
||||
public T Left;
|
||||
public T Right;
|
||||
public T Parent;
|
||||
|
||||
public T Predecessor => IntrusiveRedBlackTreeImpl<T>.PredecessorOf((T)this);
|
||||
public T Successor => IntrusiveRedBlackTreeImpl<T>.SuccessorOf((T)this);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
// NOTE: Please don't change this to struct.
|
||||
// This breaks Avalonia's TwoWay binding, which makes us unable to save new KeyboardHotkeys.
|
||||
public class KeyboardHotkeys
|
||||
{
|
||||
public Key ToggleVsync { get; set; }
|
||||
|
|
181
src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs
Normal file
181
src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs
Normal file
|
@ -0,0 +1,181 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.Extensions
|
||||
{
|
||||
public static class SequenceReaderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Dumps the entire <see cref="SequenceReader{byte}"/> to a file, restoring its previous location afterward.
|
||||
/// Useful for debugging purposes.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to write to a file</param>
|
||||
/// <param name="fileFullName">The path and name of the file to create and dump to</param>
|
||||
public static void DumpToFile(this ref SequenceReader<byte> reader, string fileFullName)
|
||||
{
|
||||
var initialConsumed = reader.Consumed;
|
||||
|
||||
reader.Rewind(initialConsumed);
|
||||
|
||||
using (var fileStream = System.IO.File.Create(fileFullName, 4096, System.IO.FileOptions.None))
|
||||
{
|
||||
while (reader.End == false)
|
||||
{
|
||||
var span = reader.CurrentSpan;
|
||||
fileStream.Write(span);
|
||||
reader.Advance(span.Length);
|
||||
}
|
||||
}
|
||||
|
||||
reader.SetConsumed(initialConsumed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the desired value. This ref should always be used. The argument passed in <paramref name="copyDestinationIfRequiredDoNotUse"/> should never be used, as this is only used for storage if the value
|
||||
/// must be copied from multiple <see cref="ReadOnlyMemory{Byte}"/> segments held by the <see cref="SequenceReader{Byte}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to get</typeparam>
|
||||
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
|
||||
/// <param name="copyDestinationIfRequiredDoNotUse">A location used as storage if (and only if) the value to be read spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments</param>
|
||||
/// <returns>A reference to the desired value, either directly to memory in the <see cref="SequenceReader{Byte}"/>, or to <paramref name="copyDestinationIfRequiredDoNotUse"/> if it has been used for copying the value in to</returns>
|
||||
/// <remarks>
|
||||
/// DO NOT use <paramref name="copyDestinationIfRequiredDoNotUse"/> after calling this method, as it will only
|
||||
/// contain a value if the value couldn't be referenced directly because it spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments.
|
||||
/// To discourage use, it is recommended to call this method like the following:
|
||||
/// <c>
|
||||
/// ref readonly MyStruct value = ref sequenceReader.GetRefOrRefToCopy{MyStruct}(out _);
|
||||
/// </c>
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception>
|
||||
public static ref readonly T GetRefOrRefToCopy<T>(this scoped ref SequenceReader<byte> reader, out T copyDestinationIfRequiredDoNotUse) where T : unmanaged
|
||||
{
|
||||
int lengthRequired = Unsafe.SizeOf<T>();
|
||||
|
||||
ReadOnlySpan<byte> span = reader.UnreadSpan;
|
||||
if (lengthRequired <= span.Length)
|
||||
{
|
||||
reader.Advance(lengthRequired);
|
||||
|
||||
copyDestinationIfRequiredDoNotUse = default;
|
||||
|
||||
ReadOnlySpan<T> spanOfT = MemoryMarshal.Cast<byte, T>(span);
|
||||
|
||||
return ref spanOfT[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
copyDestinationIfRequiredDoNotUse = default;
|
||||
|
||||
Span<T> valueSpan = MemoryMarshal.CreateSpan(ref copyDestinationIfRequiredDoNotUse, 1);
|
||||
|
||||
Span<byte> valueBytesSpan = MemoryMarshal.AsBytes(valueSpan);
|
||||
|
||||
if (!reader.TryCopyTo(valueBytesSpan))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(reader), "The sequence is not long enough to read the desired value.");
|
||||
}
|
||||
|
||||
reader.Advance(lengthRequired);
|
||||
|
||||
return ref valueSpan[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an <see cref="int"/> as little endian.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
|
||||
/// <param name="value">A location to receive the read value</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if there wasn't enough data for an <see cref="int"/></exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReadLittleEndian(this ref SequenceReader<byte> reader, out int value)
|
||||
{
|
||||
if (!reader.TryReadLittleEndian(out value))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the desired unmanaged value by copying it to the specified <paramref name="value"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to read</typeparam>
|
||||
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
|
||||
/// <param name="value">The target that will receive the read value</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReadUnmanaged<T>(this ref SequenceReader<byte> reader, out T value) where T : unmanaged
|
||||
{
|
||||
if (!reader.TryReadUnmanaged(out value))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the reader's position as bytes consumed.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to set the position</param>
|
||||
/// <param name="consumed">The number of bytes consumed</param>
|
||||
public static void SetConsumed(ref this SequenceReader<byte> reader, long consumed)
|
||||
{
|
||||
reader.Rewind(reader.Consumed);
|
||||
reader.Advance(consumed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary
|
||||
/// structs - see remarks for full details.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to read</typeparam>
|
||||
/// <remarks>
|
||||
/// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to
|
||||
/// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit
|
||||
/// overloads such as <see cref="SequenceReader{T}.TryReadLittleEndian"/>
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// True if successful. <paramref name="value"/> will be default if failed (due to lack of space).
|
||||
/// </returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe bool TryReadUnmanaged<T>(ref this SequenceReader<byte> reader, out T value) where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> span = reader.UnreadSpan;
|
||||
|
||||
if (span.Length < sizeof(T))
|
||||
{
|
||||
return TryReadUnmanagedMultiSegment(ref reader, out value);
|
||||
}
|
||||
|
||||
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(span));
|
||||
|
||||
reader.Advance(sizeof(T));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static unsafe bool TryReadUnmanagedMultiSegment<T>(ref SequenceReader<byte> reader, out T value) where T : unmanaged
|
||||
{
|
||||
Debug.Assert(reader.UnreadSpan.Length < sizeof(T));
|
||||
|
||||
// Not enough data in the current segment, try to peek for the data we need.
|
||||
T buffer = default;
|
||||
|
||||
Span<byte> tempSpan = new Span<byte>(&buffer, sizeof(T));
|
||||
|
||||
if (!reader.TryCopyTo(tempSpan))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(tempSpan));
|
||||
|
||||
reader.Advance(sizeof(T));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ using System.Threading;
|
|||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
public sealed partial class ByteMemoryPool
|
||||
public partial class ByteMemoryPool
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a <see cref="IMemoryOwner{Byte}"/> that wraps an array rented from
|
||||
|
|
|
@ -6,24 +6,8 @@ namespace Ryujinx.Common.Memory
|
|||
/// <summary>
|
||||
/// Provides a pool of re-usable byte array instances.
|
||||
/// </summary>
|
||||
public sealed partial class ByteMemoryPool
|
||||
public static partial class ByteMemoryPool
|
||||
{
|
||||
private static readonly ByteMemoryPool _shared = new();
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="ByteMemoryPool"/> instance. Private to force access through
|
||||
/// the <see cref="ByteMemoryPool.Shared"/> instance.
|
||||
/// </summary>
|
||||
private ByteMemoryPool()
|
||||
{
|
||||
// No implementation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a shared <see cref="ByteMemoryPool"/> instance.
|
||||
/// </summary>
|
||||
public static ByteMemoryPool Shared => _shared;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the maximum buffer size supported by this pool.
|
||||
/// </summary>
|
||||
|
@ -95,6 +79,20 @@ namespace Ryujinx.Common.Memory
|
|||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies <paramref name="buffer"/> into a newly rented byte memory buffer.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The byte buffer to copy</param>
|
||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory with <paramref name="buffer"/> copied to it</returns>
|
||||
public static IMemoryOwner<byte> RentCopy(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
var copy = RentImpl(buffer.Length);
|
||||
|
||||
buffer.CopyTo(copy.Memory.Span);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
private static ByteMemoryPoolBuffer RentImpl(int length)
|
||||
{
|
||||
if ((uint)length > Array.MaxLength)
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// A struct that can represent both a Span and Array.
|
||||
/// This is useful to keep the Array representation when possible to avoid copies.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Element Type</typeparam>
|
||||
public readonly ref struct SpanOrArray<T> where T : unmanaged
|
||||
{
|
||||
public readonly T[] Array;
|
||||
public readonly ReadOnlySpan<T> Span;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new SpanOrArray from an array.
|
||||
/// </summary>
|
||||
/// <param name="array">Array to store</param>
|
||||
public SpanOrArray(T[] array)
|
||||
{
|
||||
Array = array;
|
||||
Span = ReadOnlySpan<T>.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new SpanOrArray from a readonly span.
|
||||
/// </summary>
|
||||
/// <param name="array">Span to store</param>
|
||||
public SpanOrArray(ReadOnlySpan<T> span)
|
||||
{
|
||||
Array = null;
|
||||
Span = span;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the contained array, or convert the span if necessary.
|
||||
/// </summary>
|
||||
/// <returns>An array containing the data</returns>
|
||||
public T[] ToArray()
|
||||
{
|
||||
return Array ?? Span.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a ReadOnlySpan from either the array or ReadOnlySpan.
|
||||
/// </summary>
|
||||
/// <returns>A ReadOnlySpan containing the data</returns>
|
||||
public ReadOnlySpan<T> AsSpan()
|
||||
{
|
||||
return Array ?? Span;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cast an array to a SpanOrArray.
|
||||
/// </summary>
|
||||
/// <param name="array">Source array</param>
|
||||
public static implicit operator SpanOrArray<T>(T[] array)
|
||||
{
|
||||
return new SpanOrArray<T>(array);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cast a ReadOnlySpan to a SpanOrArray.
|
||||
/// </summary>
|
||||
/// <param name="span">Source ReadOnlySpan</param>
|
||||
public static implicit operator SpanOrArray<T>(ReadOnlySpan<T> span)
|
||||
{
|
||||
return new SpanOrArray<T>(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cast a Span to a SpanOrArray.
|
||||
/// </summary>
|
||||
/// <param name="span">Source Span</param>
|
||||
public static implicit operator SpanOrArray<T>(Span<T> span)
|
||||
{
|
||||
return new SpanOrArray<T>(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cast a SpanOrArray to a ReadOnlySpan
|
||||
/// </summary>
|
||||
/// <param name="spanOrArray">Source SpanOrArray</param>
|
||||
public static implicit operator ReadOnlySpan<T>(SpanOrArray<T> spanOrArray)
|
||||
{
|
||||
return spanOrArray.AsSpan();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
@ -41,6 +42,22 @@ namespace Ryujinx.Common
|
|||
return StreamUtils.StreamToBytes(stream);
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> ReadFileToRentedMemory(string filename)
|
||||
{
|
||||
var (assembly, path) = ResolveManifestPath(filename);
|
||||
|
||||
return ReadFileToRentedMemory(assembly, path);
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> ReadFileToRentedMemory(Assembly assembly, string filename)
|
||||
{
|
||||
using var stream = GetStream(assembly, filename);
|
||||
|
||||
return stream is null
|
||||
? null
|
||||
: StreamUtils.StreamToRentedMemory(stream);
|
||||
}
|
||||
|
||||
public async static Task<byte[]> ReadAsync(Assembly assembly, string filename)
|
||||
{
|
||||
using var stream = GetStream(assembly, filename);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using Microsoft.IO;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -9,12 +11,50 @@ namespace Ryujinx.Common.Utilities
|
|||
{
|
||||
public static byte[] StreamToBytes(Stream input)
|
||||
{
|
||||
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
||||
using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input);
|
||||
|
||||
return output.ToArray();
|
||||
}
|
||||
|
||||
input.CopyTo(stream);
|
||||
public static IMemoryOwner<byte> StreamToRentedMemory(Stream input)
|
||||
{
|
||||
if (input is MemoryStream inputMemoryStream)
|
||||
{
|
||||
return MemoryStreamToRentedMemory(inputMemoryStream);
|
||||
}
|
||||
else if (input.CanSeek)
|
||||
{
|
||||
long bytesExpected = input.Length;
|
||||
|
||||
return stream.ToArray();
|
||||
IMemoryOwner<byte> ownedMemory = ByteMemoryPool.Rent(bytesExpected);
|
||||
|
||||
var destSpan = ownedMemory.Memory.Span;
|
||||
|
||||
int totalBytesRead = 0;
|
||||
|
||||
while (totalBytesRead < bytesExpected)
|
||||
{
|
||||
int bytesRead = input.Read(destSpan[totalBytesRead..]);
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
ownedMemory.Dispose();
|
||||
|
||||
throw new IOException($"Tried reading {bytesExpected} but the stream closed after reading {totalBytesRead}.");
|
||||
}
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
|
||||
return ownedMemory;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If input is (non-seekable) then copy twice: first into a RecyclableMemoryStream, then to a rented IMemoryOwner<byte>.
|
||||
using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input);
|
||||
|
||||
return MemoryStreamToRentedMemory(output);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<byte[]> StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default)
|
||||
|
@ -25,5 +65,26 @@ namespace Ryujinx.Common.Utilities
|
|||
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private static IMemoryOwner<byte> MemoryStreamToRentedMemory(MemoryStream input)
|
||||
{
|
||||
input.Position = 0;
|
||||
|
||||
IMemoryOwner<byte> ownedMemory = ByteMemoryPool.Rent(input.Length);
|
||||
|
||||
// Discard the return value because we assume reading a MemoryStream always succeeds completely.
|
||||
_ = input.Read(ownedMemory.Memory.Span);
|
||||
|
||||
return ownedMemory;
|
||||
}
|
||||
|
||||
private static RecyclableMemoryStream StreamToRecyclableMemoryStream(Stream input)
|
||||
{
|
||||
RecyclableMemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
||||
|
||||
input.CopyTo(stream);
|
||||
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Collections;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
|
||||
|
@ -7,175 +5,23 @@ namespace Ryujinx.Cpu
|
|||
{
|
||||
public class AddressSpace : IDisposable
|
||||
{
|
||||
private const int DefaultBlockAlignment = 1 << 20;
|
||||
|
||||
private enum MappingType : byte
|
||||
{
|
||||
None,
|
||||
Private,
|
||||
Shared,
|
||||
}
|
||||
|
||||
private class Mapping : IntrusiveRedBlackTreeNode<Mapping>, IComparable<Mapping>
|
||||
{
|
||||
public ulong Address { get; private set; }
|
||||
public ulong Size { get; private set; }
|
||||
public ulong EndAddress => Address + Size;
|
||||
public MappingType Type { get; private set; }
|
||||
|
||||
public Mapping(ulong address, ulong size, MappingType type)
|
||||
{
|
||||
Address = address;
|
||||
Size = size;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public Mapping Split(ulong splitAddress)
|
||||
{
|
||||
ulong leftSize = splitAddress - Address;
|
||||
ulong rightSize = EndAddress - splitAddress;
|
||||
|
||||
Mapping left = new(Address, leftSize, Type);
|
||||
|
||||
Address = splitAddress;
|
||||
Size = rightSize;
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
public void UpdateState(MappingType newType)
|
||||
{
|
||||
Type = newType;
|
||||
}
|
||||
|
||||
public void Extend(ulong sizeDelta)
|
||||
{
|
||||
Size += sizeDelta;
|
||||
}
|
||||
|
||||
public int CompareTo(Mapping other)
|
||||
{
|
||||
if (Address < other.Address)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (Address <= other.EndAddress - 1UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PrivateMapping : IntrusiveRedBlackTreeNode<PrivateMapping>, IComparable<PrivateMapping>
|
||||
{
|
||||
public ulong Address { get; private set; }
|
||||
public ulong Size { get; private set; }
|
||||
public ulong EndAddress => Address + Size;
|
||||
public PrivateMemoryAllocation PrivateAllocation { get; private set; }
|
||||
|
||||
public PrivateMapping(ulong address, ulong size, PrivateMemoryAllocation privateAllocation)
|
||||
{
|
||||
Address = address;
|
||||
Size = size;
|
||||
PrivateAllocation = privateAllocation;
|
||||
}
|
||||
|
||||
public PrivateMapping Split(ulong splitAddress)
|
||||
{
|
||||
ulong leftSize = splitAddress - Address;
|
||||
ulong rightSize = EndAddress - splitAddress;
|
||||
|
||||
(var leftAllocation, PrivateAllocation) = PrivateAllocation.Split(leftSize);
|
||||
|
||||
PrivateMapping left = new(Address, leftSize, leftAllocation);
|
||||
|
||||
Address = splitAddress;
|
||||
Size = rightSize;
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
public void Map(MemoryBlock baseBlock, MemoryBlock mirrorBlock, PrivateMemoryAllocation newAllocation)
|
||||
{
|
||||
baseBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address, Size);
|
||||
mirrorBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address, Size);
|
||||
PrivateAllocation = newAllocation;
|
||||
}
|
||||
|
||||
public void Unmap(MemoryBlock baseBlock, MemoryBlock mirrorBlock)
|
||||
{
|
||||
if (PrivateAllocation.IsValid)
|
||||
{
|
||||
baseBlock.UnmapView(PrivateAllocation.Memory, Address, Size);
|
||||
mirrorBlock.UnmapView(PrivateAllocation.Memory, Address, Size);
|
||||
PrivateAllocation.Dispose();
|
||||
}
|
||||
|
||||
PrivateAllocation = default;
|
||||
}
|
||||
|
||||
public void Extend(ulong sizeDelta)
|
||||
{
|
||||
Size += sizeDelta;
|
||||
}
|
||||
|
||||
public int CompareTo(PrivateMapping other)
|
||||
{
|
||||
if (Address < other.Address)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (Address <= other.EndAddress - 1UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly PrivateMemoryAllocator _privateMemoryAllocator;
|
||||
private readonly IntrusiveRedBlackTree<Mapping> _mappingTree;
|
||||
private readonly IntrusiveRedBlackTree<PrivateMapping> _privateTree;
|
||||
|
||||
private readonly object _treeLock;
|
||||
|
||||
private readonly bool _supports4KBPages;
|
||||
|
||||
public MemoryBlock Base { get; }
|
||||
public MemoryBlock Mirror { get; }
|
||||
|
||||
public ulong AddressSpaceSize { get; }
|
||||
|
||||
public AddressSpace(MemoryBlock backingMemory, MemoryBlock baseMemory, MemoryBlock mirrorMemory, ulong addressSpaceSize, bool supports4KBPages)
|
||||
public AddressSpace(MemoryBlock backingMemory, MemoryBlock baseMemory, MemoryBlock mirrorMemory, ulong addressSpaceSize)
|
||||
{
|
||||
if (!supports4KBPages)
|
||||
{
|
||||
_privateMemoryAllocator = new PrivateMemoryAllocator(DefaultBlockAlignment, MemoryAllocationFlags.Mirrorable | MemoryAllocationFlags.NoMap);
|
||||
_mappingTree = new IntrusiveRedBlackTree<Mapping>();
|
||||
_privateTree = new IntrusiveRedBlackTree<PrivateMapping>();
|
||||
_treeLock = new object();
|
||||
|
||||
_mappingTree.Add(new Mapping(0UL, addressSpaceSize, MappingType.None));
|
||||
_privateTree.Add(new PrivateMapping(0UL, addressSpaceSize, default));
|
||||
}
|
||||
|
||||
_backingMemory = backingMemory;
|
||||
_supports4KBPages = supports4KBPages;
|
||||
|
||||
Base = baseMemory;
|
||||
Mirror = mirrorMemory;
|
||||
AddressSpaceSize = addressSpaceSize;
|
||||
}
|
||||
|
||||
public static bool TryCreate(MemoryBlock backingMemory, ulong asSize, bool supports4KBPages, out AddressSpace addressSpace)
|
||||
public static bool TryCreate(MemoryBlock backingMemory, ulong asSize, out AddressSpace addressSpace)
|
||||
{
|
||||
addressSpace = null;
|
||||
|
||||
|
@ -193,7 +39,7 @@ namespace Ryujinx.Cpu
|
|||
{
|
||||
baseMemory = new MemoryBlock(addressSpaceSize, AsFlags);
|
||||
mirrorMemory = new MemoryBlock(addressSpaceSize, AsFlags);
|
||||
addressSpace = new AddressSpace(backingMemory, baseMemory, mirrorMemory, addressSpaceSize, supports4KBPages);
|
||||
addressSpace = new AddressSpace(backingMemory, baseMemory, mirrorMemory, addressSpaceSize);
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -209,289 +55,20 @@ namespace Ryujinx.Cpu
|
|||
|
||||
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||
{
|
||||
if (_supports4KBPages)
|
||||
{
|
||||
Base.MapView(_backingMemory, pa, va, size);
|
||||
Mirror.MapView(_backingMemory, pa, va, size);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_treeLock)
|
||||
{
|
||||
ulong alignment = MemoryBlock.GetPageSize();
|
||||
bool isAligned = ((va | pa | size) & (alignment - 1)) == 0;
|
||||
|
||||
if (flags.HasFlag(MemoryMapFlags.Private) && !isAligned)
|
||||
{
|
||||
Update(va, pa, size, MappingType.Private);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The update method assumes that shared mappings are already aligned.
|
||||
|
||||
if (!flags.HasFlag(MemoryMapFlags.Private))
|
||||
{
|
||||
if ((va & (alignment - 1)) != (pa & (alignment - 1)))
|
||||
{
|
||||
throw new InvalidMemoryRegionException($"Virtual address 0x{va:X} and physical address 0x{pa:X} are misaligned and can't be aligned.");
|
||||
}
|
||||
|
||||
ulong endAddress = va + size;
|
||||
va = BitUtils.AlignDown(va, alignment);
|
||||
pa = BitUtils.AlignDown(pa, alignment);
|
||||
size = BitUtils.AlignUp(endAddress, alignment) - va;
|
||||
}
|
||||
|
||||
Update(va, pa, size, MappingType.Shared);
|
||||
}
|
||||
}
|
||||
Base.MapView(_backingMemory, pa, va, size);
|
||||
Mirror.MapView(_backingMemory, pa, va, size);
|
||||
}
|
||||
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
if (_supports4KBPages)
|
||||
{
|
||||
Base.UnmapView(_backingMemory, va, size);
|
||||
Mirror.UnmapView(_backingMemory, va, size);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_treeLock)
|
||||
{
|
||||
Update(va, 0UL, size, MappingType.None);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update(ulong va, ulong pa, ulong size, MappingType type)
|
||||
{
|
||||
Mapping map = _mappingTree.GetNode(new Mapping(va, 1UL, MappingType.None));
|
||||
|
||||
Update(map, va, pa, size, type);
|
||||
}
|
||||
|
||||
private Mapping Update(Mapping map, ulong va, ulong pa, ulong size, MappingType type)
|
||||
{
|
||||
ulong endAddress = va + size;
|
||||
|
||||
for (; map != null; map = map.Successor)
|
||||
{
|
||||
if (map.Address < va)
|
||||
{
|
||||
_mappingTree.Add(map.Split(va));
|
||||
}
|
||||
|
||||
if (map.EndAddress > endAddress)
|
||||
{
|
||||
Mapping newMap = map.Split(endAddress);
|
||||
_mappingTree.Add(newMap);
|
||||
map = newMap;
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MappingType.None:
|
||||
if (map.Type == MappingType.Shared)
|
||||
{
|
||||
ulong startOffset = map.Address - va;
|
||||
ulong mapVa = va + startOffset;
|
||||
ulong mapSize = Math.Min(size - startOffset, map.Size);
|
||||
ulong mapEndAddress = mapVa + mapSize;
|
||||
ulong alignment = MemoryBlock.GetPageSize();
|
||||
|
||||
mapVa = BitUtils.AlignDown(mapVa, alignment);
|
||||
mapEndAddress = BitUtils.AlignUp(mapEndAddress, alignment);
|
||||
|
||||
mapSize = mapEndAddress - mapVa;
|
||||
|
||||
Base.UnmapView(_backingMemory, mapVa, mapSize);
|
||||
Mirror.UnmapView(_backingMemory, mapVa, mapSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnmapPrivate(va, size);
|
||||
}
|
||||
break;
|
||||
case MappingType.Private:
|
||||
if (map.Type == MappingType.Shared)
|
||||
{
|
||||
throw new InvalidMemoryRegionException($"Private mapping request at 0x{va:X} with size 0x{size:X} overlaps shared mapping at 0x{map.Address:X} with size 0x{map.Size:X}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
MapPrivate(va, size);
|
||||
}
|
||||
break;
|
||||
case MappingType.Shared:
|
||||
if (map.Type != MappingType.None)
|
||||
{
|
||||
throw new InvalidMemoryRegionException($"Shared mapping request at 0x{va:X} with size 0x{size:X} overlaps mapping at 0x{map.Address:X} with size 0x{map.Size:X}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong startOffset = map.Address - va;
|
||||
ulong mapPa = pa + startOffset;
|
||||
ulong mapVa = va + startOffset;
|
||||
ulong mapSize = Math.Min(size - startOffset, map.Size);
|
||||
|
||||
Base.MapView(_backingMemory, mapPa, mapVa, mapSize);
|
||||
Mirror.MapView(_backingMemory, mapPa, mapVa, mapSize);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
map.UpdateState(type);
|
||||
map = TryCoalesce(map);
|
||||
|
||||
if (map.EndAddress >= endAddress)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private Mapping TryCoalesce(Mapping map)
|
||||
{
|
||||
Mapping previousMap = map.Predecessor;
|
||||
Mapping nextMap = map.Successor;
|
||||
|
||||
if (previousMap != null && CanCoalesce(previousMap, map))
|
||||
{
|
||||
previousMap.Extend(map.Size);
|
||||
_mappingTree.Remove(map);
|
||||
map = previousMap;
|
||||
}
|
||||
|
||||
if (nextMap != null && CanCoalesce(map, nextMap))
|
||||
{
|
||||
map.Extend(nextMap.Size);
|
||||
_mappingTree.Remove(nextMap);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static bool CanCoalesce(Mapping left, Mapping right)
|
||||
{
|
||||
return left.Type == right.Type;
|
||||
}
|
||||
|
||||
private void MapPrivate(ulong va, ulong size)
|
||||
{
|
||||
ulong endAddress = va + size;
|
||||
|
||||
ulong alignment = MemoryBlock.GetPageSize();
|
||||
|
||||
// Expand the range outwards based on page size to ensure that at least the requested region is mapped.
|
||||
ulong vaAligned = BitUtils.AlignDown(va, alignment);
|
||||
ulong endAddressAligned = BitUtils.AlignUp(endAddress, alignment);
|
||||
|
||||
PrivateMapping map = _privateTree.GetNode(new PrivateMapping(va, 1UL, default));
|
||||
|
||||
for (; map != null; map = map.Successor)
|
||||
{
|
||||
if (!map.PrivateAllocation.IsValid)
|
||||
{
|
||||
if (map.Address < vaAligned)
|
||||
{
|
||||
_privateTree.Add(map.Split(vaAligned));
|
||||
}
|
||||
|
||||
if (map.EndAddress > endAddressAligned)
|
||||
{
|
||||
PrivateMapping newMap = map.Split(endAddressAligned);
|
||||
_privateTree.Add(newMap);
|
||||
map = newMap;
|
||||
}
|
||||
|
||||
map.Map(Base, Mirror, _privateMemoryAllocator.Allocate(map.Size, MemoryBlock.GetPageSize()));
|
||||
}
|
||||
|
||||
if (map.EndAddress >= endAddressAligned)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UnmapPrivate(ulong va, ulong size)
|
||||
{
|
||||
ulong endAddress = va + size;
|
||||
|
||||
ulong alignment = MemoryBlock.GetPageSize();
|
||||
|
||||
// Shrink the range inwards based on page size to ensure we won't unmap memory that might be still in use.
|
||||
ulong vaAligned = BitUtils.AlignUp(va, alignment);
|
||||
ulong endAddressAligned = BitUtils.AlignDown(endAddress, alignment);
|
||||
|
||||
if (endAddressAligned <= vaAligned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PrivateMapping map = _privateTree.GetNode(new PrivateMapping(va, 1UL, default));
|
||||
|
||||
for (; map != null; map = map.Successor)
|
||||
{
|
||||
if (map.PrivateAllocation.IsValid)
|
||||
{
|
||||
if (map.Address < vaAligned)
|
||||
{
|
||||
_privateTree.Add(map.Split(vaAligned));
|
||||
}
|
||||
|
||||
if (map.EndAddress > endAddressAligned)
|
||||
{
|
||||
PrivateMapping newMap = map.Split(endAddressAligned);
|
||||
_privateTree.Add(newMap);
|
||||
map = newMap;
|
||||
}
|
||||
|
||||
map.Unmap(Base, Mirror);
|
||||
map = TryCoalesce(map);
|
||||
}
|
||||
|
||||
if (map.EndAddress >= endAddressAligned)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PrivateMapping TryCoalesce(PrivateMapping map)
|
||||
{
|
||||
PrivateMapping previousMap = map.Predecessor;
|
||||
PrivateMapping nextMap = map.Successor;
|
||||
|
||||
if (previousMap != null && CanCoalesce(previousMap, map))
|
||||
{
|
||||
previousMap.Extend(map.Size);
|
||||
_privateTree.Remove(map);
|
||||
map = previousMap;
|
||||
}
|
||||
|
||||
if (nextMap != null && CanCoalesce(map, nextMap))
|
||||
{
|
||||
map.Extend(nextMap.Size);
|
||||
_privateTree.Remove(nextMap);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static bool CanCoalesce(PrivateMapping left, PrivateMapping right)
|
||||
{
|
||||
return !left.PrivateAllocation.IsValid && !right.PrivateAllocation.IsValid;
|
||||
Base.UnmapView(_backingMemory, va, size);
|
||||
Mirror.UnmapView(_backingMemory, va, size);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
_privateMemoryAllocator?.Dispose();
|
||||
Base.Dispose();
|
||||
Mirror.Dispose();
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
|
||||
private readonly HvIpaAllocator _ipaAllocator;
|
||||
|
||||
public HvMemoryBlockAllocator(HvIpaAllocator ipaAllocator, int blockAlignment) : base(blockAlignment, MemoryAllocationFlags.None)
|
||||
public HvMemoryBlockAllocator(HvIpaAllocator ipaAllocator, ulong blockAlignment) : base(blockAlignment, MemoryAllocationFlags.None)
|
||||
{
|
||||
_ipaAllocator = ipaAllocator;
|
||||
}
|
||||
|
|
|
@ -3,12 +3,11 @@ using Ryujinx.Memory;
|
|||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu.AppleHv
|
||||
{
|
||||
|
@ -16,23 +15,8 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
/// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table.
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("macos")]
|
||||
public class HvMemoryManager : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
public sealed class HvMemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked
|
||||
{
|
||||
public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry.
|
||||
public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set.
|
||||
|
||||
private enum HostMappedPtBits : ulong
|
||||
{
|
||||
Unmapped = 0,
|
||||
Mapped,
|
||||
WriteTracked,
|
||||
ReadWriteTracked,
|
||||
|
||||
MappedReplicated = 0x5555555555555555,
|
||||
WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa,
|
||||
ReadWriteTrackedReplicated = ulong.MaxValue,
|
||||
}
|
||||
|
||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||
|
||||
private readonly HvAddressSpace _addressSpace;
|
||||
|
@ -42,9 +26,9 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly PageTable<ulong> _pageTable;
|
||||
|
||||
private readonly ulong[] _pageBitmap;
|
||||
private readonly ManagedPageFlags _pages;
|
||||
|
||||
public bool Supports4KBPages => true;
|
||||
public bool UsesPrivateAllocations => false;
|
||||
|
||||
public int AddressSpaceBits { get; }
|
||||
|
||||
|
@ -84,7 +68,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
|
||||
AddressSpaceBits = asBits;
|
||||
|
||||
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
|
||||
_pages = new ManagedPageFlags(AddressSpaceBits);
|
||||
Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler);
|
||||
}
|
||||
|
||||
|
@ -95,7 +79,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
|
||||
PtMap(va, pa, size);
|
||||
_addressSpace.MapUser(va, pa, size, MemoryPermission.ReadWriteExecute);
|
||||
AddMapping(va, size);
|
||||
_pages.AddMapping(va, size);
|
||||
|
||||
Tracking.Map(va, size);
|
||||
}
|
||||
|
@ -112,12 +96,6 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void MapForeign(ulong va, nuint hostPointer, ulong size)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
|
@ -126,7 +104,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
UnmapEvent?.Invoke(va, size);
|
||||
Tracking.Unmap(va, size);
|
||||
|
||||
RemoveMapping(va, size);
|
||||
_pages.RemoveMapping(va, size);
|
||||
_addressSpace.UnmapUser(va, size);
|
||||
PtUnmap(va, size);
|
||||
}
|
||||
|
@ -142,20 +120,11 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T Read<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T ReadTracked<T>(ulong va) where T : unmanaged
|
||||
public override T ReadTracked<T>(ulong va)
|
||||
{
|
||||
try
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
|
||||
|
||||
return Read<T>(va);
|
||||
return base.ReadTracked<T>(va);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
|
@ -168,7 +137,6 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Read(ulong va, Span<byte> data)
|
||||
{
|
||||
try
|
||||
|
@ -184,101 +152,11 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Write<T>(ulong va, T value) where T : unmanaged
|
||||
{
|
||||
Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Write(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SignalMemoryTracking(va, (ulong)data.Length, true);
|
||||
|
||||
WriteImpl(va, data);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WriteImpl(va, data);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SignalMemoryTracking(va, (ulong)data.Length, false);
|
||||
|
||||
if (IsContiguousAndMapped(va, data.Length))
|
||||
{
|
||||
var target = _backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length);
|
||||
|
||||
bool changed = !data.SequenceEqual(target);
|
||||
|
||||
if (changed)
|
||||
{
|
||||
data.CopyTo(target);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteImpl(va, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteImpl(ulong va, ReadOnlySpan<byte> data)
|
||||
public override void Write(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
try
|
||||
{
|
||||
AssertValidAddressAndSize(va, (ulong)data.Length);
|
||||
|
||||
if (IsContiguousAndMapped(va, data.Length))
|
||||
{
|
||||
data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
|
||||
}
|
||||
else
|
||||
{
|
||||
int offset = 0, size;
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressChecked(va);
|
||||
|
||||
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
||||
|
||||
data[..size].CopyTo(_backingMemory.GetSpan(pa, size));
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressChecked(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, PageSize);
|
||||
|
||||
data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size));
|
||||
}
|
||||
}
|
||||
base.Write(va, data);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
|
@ -289,61 +167,38 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
|
||||
public override void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (size == 0)
|
||||
try
|
||||
{
|
||||
return ReadOnlySpan<byte>.Empty;
|
||||
base.WriteUntracked(va, data);
|
||||
}
|
||||
|
||||
if (tracked)
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)size, false);
|
||||
}
|
||||
|
||||
if (IsContiguousAndMapped(va, size))
|
||||
{
|
||||
return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> data = new byte[size];
|
||||
|
||||
base.Read(va, data);
|
||||
|
||||
return data;
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
|
||||
public override ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false)
|
||||
{
|
||||
if (size == 0)
|
||||
try
|
||||
{
|
||||
return new WritableRegion(null, va, Memory<byte>.Empty);
|
||||
return base.GetReadOnlySequence(va, size, tracked);
|
||||
}
|
||||
|
||||
if (tracked)
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)size, true);
|
||||
}
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
if (IsContiguousAndMapped(va, size))
|
||||
{
|
||||
return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
|
||||
}
|
||||
else
|
||||
{
|
||||
Memory<byte> memory = new byte[size];
|
||||
|
||||
base.Read(va, memory.Span);
|
||||
|
||||
return new WritableRegion(this, va, memory);
|
||||
return ReadOnlySequence<byte>.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ref T GetRef<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
if (!IsContiguous(va, Unsafe.SizeOf<T>()))
|
||||
|
@ -356,26 +211,10 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
return ref _backingMemory.GetRef<T>(GetPhysicalAddressChecked(va));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsMapped(ulong va)
|
||||
public override bool IsMapped(ulong va)
|
||||
{
|
||||
return ValidateAddress(va) && IsMappedImpl(va);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsMappedImpl(ulong va)
|
||||
{
|
||||
ulong page = va >> PageBits;
|
||||
|
||||
int bit = (int)((page & 31) << 1);
|
||||
|
||||
int pageIndex = (int)(page >> PageToPteShift);
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
|
||||
return ((pte >> bit) & 3) != 0;
|
||||
return ValidateAddress(va) && _pages.IsMapped(va);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -383,91 +222,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
return IsRangeMappedImpl(va, size);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex)
|
||||
{
|
||||
startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1);
|
||||
endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1));
|
||||
|
||||
pageIndex = (int)(pageStart >> PageToPteShift);
|
||||
pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift);
|
||||
}
|
||||
|
||||
private bool IsRangeMappedImpl(ulong va, ulong size)
|
||||
{
|
||||
int pages = GetPagesCount(va, size, out _);
|
||||
|
||||
if (pages == 1)
|
||||
{
|
||||
return IsMappedImpl(va);
|
||||
}
|
||||
|
||||
ulong pageStart = va >> PageBits;
|
||||
ulong pageEnd = pageStart + (ulong)pages;
|
||||
|
||||
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||
|
||||
// Check if either bit in each 2 bit page entry is set.
|
||||
// OR the block with itself shifted down by 1, and check the first bit of each entry.
|
||||
|
||||
ulong mask = BlockMappedMask & startMask;
|
||||
|
||||
while (pageIndex <= pageEndIndex)
|
||||
{
|
||||
if (pageIndex == pageEndIndex)
|
||||
{
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
|
||||
pte |= pte >> 1;
|
||||
if ((pte & mask) != mask)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
mask = BlockMappedMask;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsContiguous(ulong va, int size)
|
||||
{
|
||||
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int pages = GetPagesCount(va, (uint)size, out va);
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
if (!ValidateAddress(va + PageSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
va += PageSize;
|
||||
}
|
||||
|
||||
return true;
|
||||
return _pages.IsRangeMapped(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -546,11 +301,10 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
return regions;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
|
||||
/// </remarks>
|
||||
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
|
||||
public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
|
@ -560,199 +314,37 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
return;
|
||||
}
|
||||
|
||||
// Software table, used for managed memory tracking.
|
||||
|
||||
int pages = GetPagesCount(va, size, out _);
|
||||
ulong pageStart = va >> PageBits;
|
||||
|
||||
if (pages == 1)
|
||||
{
|
||||
ulong tag = (ulong)(write ? HostMappedPtBits.WriteTracked : HostMappedPtBits.ReadWriteTracked);
|
||||
|
||||
int bit = (int)((pageStart & 31) << 1);
|
||||
|
||||
int pageIndex = (int)(pageStart >> PageToPteShift);
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
ulong state = ((pte >> bit) & 3);
|
||||
|
||||
if (state >= tag)
|
||||
{
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
|
||||
return;
|
||||
}
|
||||
else if (state == 0)
|
||||
{
|
||||
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong pageEnd = pageStart + (ulong)pages;
|
||||
|
||||
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||
|
||||
ulong mask = startMask;
|
||||
|
||||
ulong anyTrackingTag = (ulong)HostMappedPtBits.WriteTrackedReplicated;
|
||||
|
||||
while (pageIndex <= pageEndIndex)
|
||||
{
|
||||
if (pageIndex == pageEndIndex)
|
||||
{
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
ulong mappedMask = mask & BlockMappedMask;
|
||||
|
||||
ulong mappedPte = pte | (pte >> 1);
|
||||
if ((mappedPte & mappedMask) != mappedMask)
|
||||
{
|
||||
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
|
||||
}
|
||||
|
||||
pte &= mask;
|
||||
if ((pte & anyTrackingTag) != 0) // Search for any tracking.
|
||||
{
|
||||
// Writes trigger any tracking.
|
||||
// Only trigger tracking from reads if both bits are set on any page.
|
||||
if (write || (pte & (pte >> 1) & BlockMappedMask) != 0)
|
||||
{
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mask = ulong.MaxValue;
|
||||
}
|
||||
}
|
||||
_pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of pages in a virtual address range.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range</param>
|
||||
/// <param name="startVa">The virtual address of the beginning of the first page</param>
|
||||
/// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetPagesCount(ulong va, ulong size, out ulong startVa)
|
||||
{
|
||||
// WARNING: Always check if ulong does not overflow during the operations.
|
||||
startVa = va & ~(ulong)PageMask;
|
||||
ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
|
||||
|
||||
return (int)(vaSpan / PageSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest)
|
||||
{
|
||||
// Protection is inverted on software pages, since the default value is 0.
|
||||
protection = (~protection) & MemoryPermission.ReadAndWrite;
|
||||
|
||||
int pages = GetPagesCount(va, size, out va);
|
||||
ulong pageStart = va >> PageBits;
|
||||
|
||||
if (pages == 1)
|
||||
if (guest)
|
||||
{
|
||||
ulong protTag = protection switch
|
||||
{
|
||||
MemoryPermission.None => (ulong)HostMappedPtBits.Mapped,
|
||||
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTracked,
|
||||
_ => (ulong)HostMappedPtBits.ReadWriteTracked,
|
||||
};
|
||||
|
||||
int bit = (int)((pageStart & 31) << 1);
|
||||
|
||||
ulong tagMask = 3UL << bit;
|
||||
ulong invTagMask = ~tagMask;
|
||||
|
||||
ulong tag = protTag << bit;
|
||||
|
||||
int pageIndex = (int)(pageStart >> PageToPteShift);
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||
|
||||
ulong pte;
|
||||
|
||||
do
|
||||
{
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
}
|
||||
while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
|
||||
_addressSpace.ReprotectUser(va, size, protection);
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong pageEnd = pageStart + (ulong)pages;
|
||||
|
||||
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||
|
||||
ulong mask = startMask;
|
||||
|
||||
ulong protTag = protection switch
|
||||
{
|
||||
MemoryPermission.None => (ulong)HostMappedPtBits.MappedReplicated,
|
||||
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTrackedReplicated,
|
||||
_ => (ulong)HostMappedPtBits.ReadWriteTrackedReplicated,
|
||||
};
|
||||
|
||||
while (pageIndex <= pageEndIndex)
|
||||
{
|
||||
if (pageIndex == pageEndIndex)
|
||||
{
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
|
||||
ulong pte;
|
||||
ulong mappedMask;
|
||||
|
||||
// Change the protection of all 2 bit entries that are mapped.
|
||||
do
|
||||
{
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
|
||||
mappedMask = pte | (pte >> 1);
|
||||
mappedMask |= (mappedMask & BlockMappedMask) << 1;
|
||||
mappedMask &= mask; // Only update mapped pages within the given range.
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte);
|
||||
|
||||
mask = ulong.MaxValue;
|
||||
}
|
||||
_pages.TrackingReprotect(va, size, protection);
|
||||
}
|
||||
|
||||
protection = protection switch
|
||||
{
|
||||
MemoryPermission.None => MemoryPermission.ReadAndWrite,
|
||||
MemoryPermission.Write => MemoryPermission.Read,
|
||||
_ => MemoryPermission.None,
|
||||
};
|
||||
|
||||
_addressSpace.ReprotectUser(va, size, protection);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RegionHandle BeginTracking(ulong address, ulong size, int id)
|
||||
public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None)
|
||||
{
|
||||
return Tracking.BeginTracking(address, size, id);
|
||||
return Tracking.BeginTracking(address, size, id, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
|
||||
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None)
|
||||
{
|
||||
return Tracking.BeginGranularTracking(address, size, handles, granularity, id);
|
||||
return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -761,87 +353,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given address mapping to the page table.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual memory address</param>
|
||||
/// <param name="size">Size to be mapped</param>
|
||||
private void AddMapping(ulong va, ulong size)
|
||||
{
|
||||
int pages = GetPagesCount(va, size, out _);
|
||||
ulong pageStart = va >> PageBits;
|
||||
ulong pageEnd = pageStart + (ulong)pages;
|
||||
|
||||
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||
|
||||
ulong mask = startMask;
|
||||
|
||||
while (pageIndex <= pageEndIndex)
|
||||
{
|
||||
if (pageIndex == pageEndIndex)
|
||||
{
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
|
||||
ulong pte;
|
||||
ulong mappedMask;
|
||||
|
||||
// Map all 2-bit entries that are unmapped.
|
||||
do
|
||||
{
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
|
||||
mappedMask = pte | (pte >> 1);
|
||||
mappedMask |= (mappedMask & BlockMappedMask) << 1;
|
||||
mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged.
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte);
|
||||
|
||||
mask = ulong.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the given address mapping from the page table.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual memory address</param>
|
||||
/// <param name="size">Size to be unmapped</param>
|
||||
private void RemoveMapping(ulong va, ulong size)
|
||||
{
|
||||
int pages = GetPagesCount(va, size, out _);
|
||||
ulong pageStart = va >> PageBits;
|
||||
ulong pageEnd = pageStart + (ulong)pages;
|
||||
|
||||
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||
|
||||
startMask = ~startMask;
|
||||
endMask = ~endMask;
|
||||
|
||||
ulong mask = startMask;
|
||||
|
||||
while (pageIndex <= pageEndIndex)
|
||||
{
|
||||
if (pageIndex == pageEndIndex)
|
||||
{
|
||||
mask |= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
ulong pte;
|
||||
|
||||
do
|
||||
{
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte);
|
||||
|
||||
mask = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private ulong GetPhysicalAddressChecked(ulong va)
|
||||
private nuint GetPhysicalAddressChecked(ulong va)
|
||||
{
|
||||
if (!IsMapped(va))
|
||||
{
|
||||
|
@ -851,9 +363,9 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
return GetPhysicalAddressInternal(va);
|
||||
}
|
||||
|
||||
private ulong GetPhysicalAddressInternal(ulong va)
|
||||
private nuint GetPhysicalAddressInternal(ulong va)
|
||||
{
|
||||
return _pageTable.Read(va) + (va & PageMask);
|
||||
return (nuint)(_pageTable.Read(va) + (va & PageMask));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -864,10 +376,17 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
_addressSpace.Dispose();
|
||||
}
|
||||
|
||||
protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size)
|
||||
protected override Memory<byte> GetPhysicalAddressMemory(nuint pa, int size)
|
||||
=> _backingMemory.GetMemory(pa, size);
|
||||
|
||||
protected override Span<byte> GetPhysicalAddressSpan(nuint pa, int size)
|
||||
=> _backingMemory.GetSpan(pa, size);
|
||||
|
||||
protected override ulong TranslateVirtualAddressForRead(ulong va)
|
||||
protected override nuint TranslateVirtualAddressChecked(ulong va)
|
||||
=> GetPhysicalAddressChecked(va);
|
||||
|
||||
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
|
||||
=> GetPhysicalAddressInternal(va);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,8 +28,9 @@ namespace Ryujinx.Cpu
|
|||
/// <param name="address">CPU virtual address of the region</param>
|
||||
/// <param name="size">Size of the region</param>
|
||||
/// <param name="id">Handle ID</param>
|
||||
/// <param name="flags">Region flags</param>
|
||||
/// <returns>The memory tracking handle</returns>
|
||||
RegionHandle BeginTracking(ulong address, ulong size, int id);
|
||||
RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None);
|
||||
|
||||
/// <summary>
|
||||
/// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
|
||||
|
@ -39,8 +40,9 @@ namespace Ryujinx.Cpu
|
|||
/// <param name="handles">Handles to inherit state from or reuse. When none are present, provide null</param>
|
||||
/// <param name="granularity">Desired granularity of write tracking</param>
|
||||
/// <param name="id">Handle ID</param>
|
||||
/// <param name="flags">Region flags</param>
|
||||
/// <returns>The memory tracking handle</returns>
|
||||
MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id);
|
||||
MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None);
|
||||
|
||||
/// <summary>
|
||||
/// Obtains a smart memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
using Ryujinx.Common.Collections;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit.HostTracked
|
||||
{
|
||||
internal class AddressIntrusiveRedBlackTree<T> : IntrusiveRedBlackTree<T> where T : IntrusiveRedBlackTreeNode<T>, IComparable<T>, IComparable<ulong>
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieve the node that is considered equal to the specified address by the comparator.
|
||||
/// </summary>
|
||||
/// <param name="address">Address to compare with</param>
|
||||
/// <returns>Node that is equal to <paramref name="address"/></returns>
|
||||
public T GetNode(ulong address)
|
||||
{
|
||||
T node = Root;
|
||||
while (node != null)
|
||||
{
|
||||
int cmp = node.CompareTo(address);
|
||||
if (cmp < 0)
|
||||
{
|
||||
node = node.Left;
|
||||
}
|
||||
else if (cmp > 0)
|
||||
{
|
||||
node = node.Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
708
src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartition.cs
Normal file
708
src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartition.cs
Normal file
|
@ -0,0 +1,708 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Collections;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit.HostTracked
|
||||
{
|
||||
readonly struct PrivateRange
|
||||
{
|
||||
public readonly MemoryBlock Memory;
|
||||
public readonly ulong Offset;
|
||||
public readonly ulong Size;
|
||||
|
||||
public static PrivateRange Empty => new(null, 0, 0);
|
||||
|
||||
public PrivateRange(MemoryBlock memory, ulong offset, ulong size)
|
||||
{
|
||||
Memory = memory;
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
class AddressSpacePartition : IDisposable
|
||||
{
|
||||
public const ulong GuestPageSize = 0x1000;
|
||||
|
||||
private const int DefaultBlockAlignment = 1 << 20;
|
||||
|
||||
private enum MappingType : byte
|
||||
{
|
||||
None,
|
||||
Private,
|
||||
}
|
||||
|
||||
private class Mapping : IntrusiveRedBlackTreeNode<Mapping>, IComparable<Mapping>, IComparable<ulong>
|
||||
{
|
||||
public ulong Address { get; private set; }
|
||||
public ulong Size { get; private set; }
|
||||
public ulong EndAddress => Address + Size;
|
||||
public MappingType Type { get; private set; }
|
||||
|
||||
public Mapping(ulong address, ulong size, MappingType type)
|
||||
{
|
||||
Address = address;
|
||||
Size = size;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public Mapping Split(ulong splitAddress)
|
||||
{
|
||||
ulong leftSize = splitAddress - Address;
|
||||
ulong rightSize = EndAddress - splitAddress;
|
||||
|
||||
Mapping left = new(Address, leftSize, Type);
|
||||
|
||||
Address = splitAddress;
|
||||
Size = rightSize;
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
public void UpdateState(MappingType newType)
|
||||
{
|
||||
Type = newType;
|
||||
}
|
||||
|
||||
public void Extend(ulong sizeDelta)
|
||||
{
|
||||
Size += sizeDelta;
|
||||
}
|
||||
|
||||
public int CompareTo(Mapping other)
|
||||
{
|
||||
if (Address < other.Address)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (Address <= other.EndAddress - 1UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
public int CompareTo(ulong address)
|
||||
{
|
||||
if (address < Address)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (address <= EndAddress - 1UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PrivateMapping : IntrusiveRedBlackTreeNode<PrivateMapping>, IComparable<PrivateMapping>, IComparable<ulong>
|
||||
{
|
||||
public ulong Address { get; private set; }
|
||||
public ulong Size { get; private set; }
|
||||
public ulong EndAddress => Address + Size;
|
||||
public PrivateMemoryAllocation PrivateAllocation { get; private set; }
|
||||
|
||||
public PrivateMapping(ulong address, ulong size, PrivateMemoryAllocation privateAllocation)
|
||||
{
|
||||
Address = address;
|
||||
Size = size;
|
||||
PrivateAllocation = privateAllocation;
|
||||
}
|
||||
|
||||
public PrivateMapping Split(ulong splitAddress)
|
||||
{
|
||||
ulong leftSize = splitAddress - Address;
|
||||
ulong rightSize = EndAddress - splitAddress;
|
||||
|
||||
Debug.Assert(leftSize > 0);
|
||||
Debug.Assert(rightSize > 0);
|
||||
|
||||
(var leftAllocation, PrivateAllocation) = PrivateAllocation.Split(leftSize);
|
||||
|
||||
PrivateMapping left = new(Address, leftSize, leftAllocation);
|
||||
|
||||
Address = splitAddress;
|
||||
Size = rightSize;
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
public void Map(AddressSpacePartitionMultiAllocation baseBlock, ulong baseAddress, PrivateMemoryAllocation newAllocation)
|
||||
{
|
||||
baseBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address - baseAddress, Size);
|
||||
PrivateAllocation = newAllocation;
|
||||
}
|
||||
|
||||
public void Unmap(AddressSpacePartitionMultiAllocation baseBlock, ulong baseAddress)
|
||||
{
|
||||
if (PrivateAllocation.IsValid)
|
||||
{
|
||||
baseBlock.UnmapView(PrivateAllocation.Memory, Address - baseAddress, Size);
|
||||
PrivateAllocation.Dispose();
|
||||
}
|
||||
|
||||
PrivateAllocation = default;
|
||||
}
|
||||
|
||||
public void Extend(ulong sizeDelta)
|
||||
{
|
||||
Size += sizeDelta;
|
||||
}
|
||||
|
||||
public int CompareTo(PrivateMapping other)
|
||||
{
|
||||
if (Address < other.Address)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (Address <= other.EndAddress - 1UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
public int CompareTo(ulong address)
|
||||
{
|
||||
if (address < Address)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (address <= EndAddress - 1UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly AddressSpacePartitionMultiAllocation _baseMemory;
|
||||
private readonly PrivateMemoryAllocator _privateMemoryAllocator;
|
||||
|
||||
private readonly AddressIntrusiveRedBlackTree<Mapping> _mappingTree;
|
||||
private readonly AddressIntrusiveRedBlackTree<PrivateMapping> _privateTree;
|
||||
|
||||
private readonly ReaderWriterLockSlim _treeLock;
|
||||
|
||||
private readonly ulong _hostPageSize;
|
||||
|
||||
private ulong? _firstPagePa;
|
||||
private ulong? _lastPagePa;
|
||||
private ulong _cachedFirstPagePa;
|
||||
private ulong _cachedLastPagePa;
|
||||
private MemoryBlock _firstPageMemoryForUnmap;
|
||||
private ulong _firstPageOffsetForLateMap;
|
||||
private MemoryPermission _firstPageMemoryProtection;
|
||||
|
||||
public ulong Address { get; }
|
||||
public ulong Size { get; }
|
||||
public ulong EndAddress => Address + Size;
|
||||
|
||||
public AddressSpacePartition(AddressSpacePartitionAllocation baseMemory, MemoryBlock backingMemory, ulong address, ulong size)
|
||||
{
|
||||
_privateMemoryAllocator = new PrivateMemoryAllocator(DefaultBlockAlignment, MemoryAllocationFlags.Mirrorable);
|
||||
_mappingTree = new AddressIntrusiveRedBlackTree<Mapping>();
|
||||
_privateTree = new AddressIntrusiveRedBlackTree<PrivateMapping>();
|
||||
_treeLock = new ReaderWriterLockSlim();
|
||||
|
||||
_mappingTree.Add(new Mapping(address, size, MappingType.None));
|
||||
_privateTree.Add(new PrivateMapping(address, size, default));
|
||||
|
||||
_hostPageSize = MemoryBlock.GetPageSize();
|
||||
|
||||
_backingMemory = backingMemory;
|
||||
_baseMemory = new(baseMemory);
|
||||
|
||||
_cachedFirstPagePa = ulong.MaxValue;
|
||||
_cachedLastPagePa = ulong.MaxValue;
|
||||
|
||||
Address = address;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public bool IsEmpty()
|
||||
{
|
||||
_treeLock.EnterReadLock();
|
||||
|
||||
try
|
||||
{
|
||||
Mapping map = _mappingTree.GetNode(Address);
|
||||
|
||||
return map != null && map.Address == Address && map.Size == Size && map.Type == MappingType.None;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_treeLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Map(ulong va, ulong pa, ulong size)
|
||||
{
|
||||
Debug.Assert(va >= Address);
|
||||
Debug.Assert(va + size <= EndAddress);
|
||||
|
||||
if (va == Address)
|
||||
{
|
||||
_firstPagePa = pa;
|
||||
}
|
||||
|
||||
if (va <= EndAddress - GuestPageSize && va + size > EndAddress - GuestPageSize)
|
||||
{
|
||||
_lastPagePa = pa + ((EndAddress - GuestPageSize) - va);
|
||||
}
|
||||
|
||||
Update(va, pa, size, MappingType.Private);
|
||||
}
|
||||
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
Debug.Assert(va >= Address);
|
||||
Debug.Assert(va + size <= EndAddress);
|
||||
|
||||
if (va == Address)
|
||||
{
|
||||
_firstPagePa = null;
|
||||
}
|
||||
|
||||
if (va <= EndAddress - GuestPageSize && va + size > EndAddress - GuestPageSize)
|
||||
{
|
||||
_lastPagePa = null;
|
||||
}
|
||||
|
||||
Update(va, 0UL, size, MappingType.None);
|
||||
}
|
||||
|
||||
public void ReprotectAligned(ulong va, ulong size, MemoryPermission protection)
|
||||
{
|
||||
Debug.Assert(va >= Address);
|
||||
Debug.Assert(va + size <= EndAddress);
|
||||
|
||||
_baseMemory.Reprotect(va - Address, size, protection, false);
|
||||
|
||||
if (va == Address)
|
||||
{
|
||||
_firstPageMemoryProtection = protection;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reprotect(
|
||||
ulong va,
|
||||
ulong size,
|
||||
MemoryPermission protection,
|
||||
AddressSpacePartitioned addressSpace,
|
||||
Action<ulong, IntPtr, ulong> updatePtCallback)
|
||||
{
|
||||
if (_baseMemory.LazyInitMirrorForProtection(addressSpace, Address, Size, protection))
|
||||
{
|
||||
LateMap();
|
||||
}
|
||||
|
||||
updatePtCallback(va, _baseMemory.GetPointerForProtection(va - Address, size, protection), size);
|
||||
}
|
||||
|
||||
public IntPtr GetPointer(ulong va, ulong size)
|
||||
{
|
||||
Debug.Assert(va >= Address);
|
||||
Debug.Assert(va + size <= EndAddress);
|
||||
|
||||
return _baseMemory.GetPointer(va - Address, size);
|
||||
}
|
||||
|
||||
public void InsertBridgeAtEnd(AddressSpacePartition partitionAfter, bool useProtectionMirrors)
|
||||
{
|
||||
ulong firstPagePa = partitionAfter?._firstPagePa ?? ulong.MaxValue;
|
||||
ulong lastPagePa = _lastPagePa ?? ulong.MaxValue;
|
||||
|
||||
if (firstPagePa != _cachedFirstPagePa || lastPagePa != _cachedLastPagePa)
|
||||
{
|
||||
if (partitionAfter != null && partitionAfter._firstPagePa.HasValue)
|
||||
{
|
||||
(MemoryBlock firstPageMemory, ulong firstPageOffset) = partitionAfter.GetFirstPageMemoryAndOffset();
|
||||
|
||||
_baseMemory.MapView(firstPageMemory, firstPageOffset, Size, _hostPageSize);
|
||||
|
||||
if (!useProtectionMirrors)
|
||||
{
|
||||
_baseMemory.Reprotect(Size, _hostPageSize, partitionAfter._firstPageMemoryProtection, throwOnFail: false);
|
||||
}
|
||||
|
||||
_firstPageMemoryForUnmap = firstPageMemory;
|
||||
_firstPageOffsetForLateMap = firstPageOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
MemoryBlock firstPageMemoryForUnmap = _firstPageMemoryForUnmap;
|
||||
|
||||
if (firstPageMemoryForUnmap != null)
|
||||
{
|
||||
_baseMemory.UnmapView(firstPageMemoryForUnmap, Size, _hostPageSize);
|
||||
_firstPageMemoryForUnmap = null;
|
||||
}
|
||||
}
|
||||
|
||||
_cachedFirstPagePa = firstPagePa;
|
||||
_cachedLastPagePa = lastPagePa;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReprotectBridge(MemoryPermission protection)
|
||||
{
|
||||
if (_firstPageMemoryForUnmap != null)
|
||||
{
|
||||
_baseMemory.Reprotect(Size, _hostPageSize, protection, throwOnFail: false);
|
||||
}
|
||||
}
|
||||
|
||||
private (MemoryBlock, ulong) GetFirstPageMemoryAndOffset()
|
||||
{
|
||||
_treeLock.EnterReadLock();
|
||||
|
||||
try
|
||||
{
|
||||
PrivateMapping map = _privateTree.GetNode(Address);
|
||||
|
||||
if (map != null && map.PrivateAllocation.IsValid)
|
||||
{
|
||||
return (map.PrivateAllocation.Memory, map.PrivateAllocation.Offset + (Address - map.Address));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_treeLock.ExitReadLock();
|
||||
}
|
||||
|
||||
return (_backingMemory, _firstPagePa.Value);
|
||||
}
|
||||
|
||||
public PrivateRange GetPrivateAllocation(ulong va)
|
||||
{
|
||||
_treeLock.EnterReadLock();
|
||||
|
||||
try
|
||||
{
|
||||
PrivateMapping map = _privateTree.GetNode(va);
|
||||
|
||||
if (map != null && map.PrivateAllocation.IsValid)
|
||||
{
|
||||
return new(map.PrivateAllocation.Memory, map.PrivateAllocation.Offset + (va - map.Address), map.Size - (va - map.Address));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_treeLock.ExitReadLock();
|
||||
}
|
||||
|
||||
return PrivateRange.Empty;
|
||||
}
|
||||
|
||||
private void Update(ulong va, ulong pa, ulong size, MappingType type)
|
||||
{
|
||||
_treeLock.EnterWriteLock();
|
||||
|
||||
try
|
||||
{
|
||||
Mapping map = _mappingTree.GetNode(va);
|
||||
|
||||
Update(map, va, pa, size, type);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_treeLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
private Mapping Update(Mapping map, ulong va, ulong pa, ulong size, MappingType type)
|
||||
{
|
||||
ulong endAddress = va + size;
|
||||
|
||||
for (; map != null; map = map.Successor)
|
||||
{
|
||||
if (map.Address < va)
|
||||
{
|
||||
_mappingTree.Add(map.Split(va));
|
||||
}
|
||||
|
||||
if (map.EndAddress > endAddress)
|
||||
{
|
||||
Mapping newMap = map.Split(endAddress);
|
||||
_mappingTree.Add(newMap);
|
||||
map = newMap;
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MappingType.None:
|
||||
ulong alignment = _hostPageSize;
|
||||
|
||||
bool unmappedBefore = map.Predecessor == null ||
|
||||
(map.Predecessor.Type == MappingType.None && map.Predecessor.Address <= BitUtils.AlignDown(va, alignment));
|
||||
|
||||
bool unmappedAfter = map.Successor == null ||
|
||||
(map.Successor.Type == MappingType.None && map.Successor.EndAddress >= BitUtils.AlignUp(endAddress, alignment));
|
||||
|
||||
UnmapPrivate(va, size, unmappedBefore, unmappedAfter);
|
||||
break;
|
||||
case MappingType.Private:
|
||||
MapPrivate(va, size);
|
||||
break;
|
||||
}
|
||||
|
||||
map.UpdateState(type);
|
||||
map = TryCoalesce(map);
|
||||
|
||||
if (map.EndAddress >= endAddress)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private Mapping TryCoalesce(Mapping map)
|
||||
{
|
||||
Mapping previousMap = map.Predecessor;
|
||||
Mapping nextMap = map.Successor;
|
||||
|
||||
if (previousMap != null && CanCoalesce(previousMap, map))
|
||||
{
|
||||
previousMap.Extend(map.Size);
|
||||
_mappingTree.Remove(map);
|
||||
map = previousMap;
|
||||
}
|
||||
|
||||
if (nextMap != null && CanCoalesce(map, nextMap))
|
||||
{
|
||||
map.Extend(nextMap.Size);
|
||||
_mappingTree.Remove(nextMap);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static bool CanCoalesce(Mapping left, Mapping right)
|
||||
{
|
||||
return left.Type == right.Type;
|
||||
}
|
||||
|
||||
private void MapPrivate(ulong va, ulong size)
|
||||
{
|
||||
ulong endAddress = va + size;
|
||||
|
||||
ulong alignment = _hostPageSize;
|
||||
|
||||
// Expand the range outwards based on page size to ensure that at least the requested region is mapped.
|
||||
ulong vaAligned = BitUtils.AlignDown(va, alignment);
|
||||
ulong endAddressAligned = BitUtils.AlignUp(endAddress, alignment);
|
||||
|
||||
PrivateMapping map = _privateTree.GetNode(va);
|
||||
|
||||
for (; map != null; map = map.Successor)
|
||||
{
|
||||
if (!map.PrivateAllocation.IsValid)
|
||||
{
|
||||
if (map.Address < vaAligned)
|
||||
{
|
||||
_privateTree.Add(map.Split(vaAligned));
|
||||
}
|
||||
|
||||
if (map.EndAddress > endAddressAligned)
|
||||
{
|
||||
PrivateMapping newMap = map.Split(endAddressAligned);
|
||||
_privateTree.Add(newMap);
|
||||
map = newMap;
|
||||
}
|
||||
|
||||
map.Map(_baseMemory, Address, _privateMemoryAllocator.Allocate(map.Size, _hostPageSize));
|
||||
}
|
||||
|
||||
if (map.EndAddress >= endAddressAligned)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UnmapPrivate(ulong va, ulong size, bool unmappedBefore, bool unmappedAfter)
|
||||
{
|
||||
ulong endAddress = va + size;
|
||||
|
||||
ulong alignment = _hostPageSize;
|
||||
|
||||
// If the adjacent mappings are unmapped, expand the range outwards,
|
||||
// otherwise shrink it inwards. We must ensure we won't unmap pages that might still be in use.
|
||||
ulong vaAligned = unmappedBefore ? BitUtils.AlignDown(va, alignment) : BitUtils.AlignUp(va, alignment);
|
||||
ulong endAddressAligned = unmappedAfter ? BitUtils.AlignUp(endAddress, alignment) : BitUtils.AlignDown(endAddress, alignment);
|
||||
|
||||
if (endAddressAligned <= vaAligned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PrivateMapping map = _privateTree.GetNode(vaAligned);
|
||||
|
||||
for (; map != null; map = map.Successor)
|
||||
{
|
||||
if (map.PrivateAllocation.IsValid)
|
||||
{
|
||||
if (map.Address < vaAligned)
|
||||
{
|
||||
_privateTree.Add(map.Split(vaAligned));
|
||||
}
|
||||
|
||||
if (map.EndAddress > endAddressAligned)
|
||||
{
|
||||
PrivateMapping newMap = map.Split(endAddressAligned);
|
||||
_privateTree.Add(newMap);
|
||||
map = newMap;
|
||||
}
|
||||
|
||||
map.Unmap(_baseMemory, Address);
|
||||
map = TryCoalesce(map);
|
||||
}
|
||||
|
||||
if (map.EndAddress >= endAddressAligned)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PrivateMapping TryCoalesce(PrivateMapping map)
|
||||
{
|
||||
PrivateMapping previousMap = map.Predecessor;
|
||||
PrivateMapping nextMap = map.Successor;
|
||||
|
||||
if (previousMap != null && CanCoalesce(previousMap, map))
|
||||
{
|
||||
previousMap.Extend(map.Size);
|
||||
_privateTree.Remove(map);
|
||||
map = previousMap;
|
||||
}
|
||||
|
||||
if (nextMap != null && CanCoalesce(map, nextMap))
|
||||
{
|
||||
map.Extend(nextMap.Size);
|
||||
_privateTree.Remove(nextMap);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static bool CanCoalesce(PrivateMapping left, PrivateMapping right)
|
||||
{
|
||||
return !left.PrivateAllocation.IsValid && !right.PrivateAllocation.IsValid;
|
||||
}
|
||||
|
||||
private void LateMap()
|
||||
{
|
||||
// Map all existing private allocations.
|
||||
// This is necessary to ensure mirrors that are lazily created have the same mappings as the main one.
|
||||
|
||||
PrivateMapping map = _privateTree.GetNode(Address);
|
||||
|
||||
for (; map != null; map = map.Successor)
|
||||
{
|
||||
if (map.PrivateAllocation.IsValid)
|
||||
{
|
||||
_baseMemory.LateMapView(map.PrivateAllocation.Memory, map.PrivateAllocation.Offset, map.Address - Address, map.Size);
|
||||
}
|
||||
}
|
||||
|
||||
MemoryBlock firstPageMemory = _firstPageMemoryForUnmap;
|
||||
ulong firstPageOffset = _firstPageOffsetForLateMap;
|
||||
|
||||
if (firstPageMemory != null)
|
||||
{
|
||||
_baseMemory.LateMapView(firstPageMemory, firstPageOffset, Size, _hostPageSize);
|
||||
}
|
||||
}
|
||||
|
||||
public PrivateRange GetFirstPrivateAllocation(ulong va, ulong size, out ulong nextVa)
|
||||
{
|
||||
_treeLock.EnterReadLock();
|
||||
|
||||
try
|
||||
{
|
||||
PrivateMapping map = _privateTree.GetNode(va);
|
||||
|
||||
nextVa = map.EndAddress;
|
||||
|
||||
if (map != null && map.PrivateAllocation.IsValid)
|
||||
{
|
||||
ulong startOffset = va - map.Address;
|
||||
|
||||
return new(
|
||||
map.PrivateAllocation.Memory,
|
||||
map.PrivateAllocation.Offset + startOffset,
|
||||
Math.Min(map.PrivateAllocation.Size - startOffset, size));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_treeLock.ExitReadLock();
|
||||
}
|
||||
|
||||
return PrivateRange.Empty;
|
||||
}
|
||||
|
||||
public bool HasPrivateAllocation(ulong va, ulong size, ulong startVa, ulong startSize, ref PrivateRange range)
|
||||
{
|
||||
ulong endVa = va + size;
|
||||
|
||||
_treeLock.EnterReadLock();
|
||||
|
||||
try
|
||||
{
|
||||
for (PrivateMapping map = _privateTree.GetNode(va); map != null && map.Address < endVa; map = map.Successor)
|
||||
{
|
||||
if (map.PrivateAllocation.IsValid)
|
||||
{
|
||||
if (map.Address <= startVa && map.EndAddress >= startVa + startSize)
|
||||
{
|
||||
ulong startOffset = startVa - map.Address;
|
||||
|
||||
range = new(
|
||||
map.PrivateAllocation.Memory,
|
||||
map.PrivateAllocation.Offset + startOffset,
|
||||
Math.Min(map.PrivateAllocation.Size - startOffset, startSize));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_treeLock.ExitReadLock();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
_privateMemoryAllocator.Dispose();
|
||||
_baseMemory.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Collections;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit.HostTracked
|
||||
{
|
||||
readonly struct AddressSpacePartitionAllocation : IDisposable
|
||||
{
|
||||
private readonly AddressSpacePartitionAllocator _owner;
|
||||
private readonly PrivateMemoryAllocatorImpl<AddressSpacePartitionAllocator.Block>.Allocation _allocation;
|
||||
|
||||
public IntPtr Pointer => (IntPtr)((ulong)_allocation.Block.Memory.Pointer + _allocation.Offset);
|
||||
|
||||
public bool IsValid => _owner != null;
|
||||
|
||||
public AddressSpacePartitionAllocation(
|
||||
AddressSpacePartitionAllocator owner,
|
||||
PrivateMemoryAllocatorImpl<AddressSpacePartitionAllocator.Block>.Allocation allocation)
|
||||
{
|
||||
_owner = owner;
|
||||
_allocation = allocation;
|
||||
}
|
||||
|
||||
public void RegisterMapping(ulong va, ulong endVa)
|
||||
{
|
||||
_allocation.Block.AddMapping(_allocation.Offset, _allocation.Size, va, endVa);
|
||||
}
|
||||
|
||||
public void MapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size)
|
||||
{
|
||||
_allocation.Block.Memory.MapView(srcBlock, srcOffset, _allocation.Offset + dstOffset, size);
|
||||
}
|
||||
|
||||
public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size)
|
||||
{
|
||||
_allocation.Block.Memory.UnmapView(srcBlock, _allocation.Offset + offset, size);
|
||||
}
|
||||
|
||||
public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail)
|
||||
{
|
||||
_allocation.Block.Memory.Reprotect(_allocation.Offset + offset, size, permission, throwOnFail);
|
||||
}
|
||||
|
||||
public IntPtr GetPointer(ulong offset, ulong size)
|
||||
{
|
||||
return _allocation.Block.Memory.GetPointer(_allocation.Offset + offset, size);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_allocation.Block.RemoveMapping(_allocation.Offset, _allocation.Size);
|
||||
_owner.Free(_allocation.Block, _allocation.Offset, _allocation.Size);
|
||||
}
|
||||
}
|
||||
|
||||
class AddressSpacePartitionAllocator : PrivateMemoryAllocatorImpl<AddressSpacePartitionAllocator.Block>
|
||||
{
|
||||
private const ulong DefaultBlockAlignment = 1UL << 32; // 4GB
|
||||
|
||||
public class Block : PrivateMemoryAllocator.Block
|
||||
{
|
||||
private readonly MemoryTracking _tracking;
|
||||
private readonly Func<ulong, ulong> _readPtCallback;
|
||||
private readonly MemoryEhMeilleure _memoryEh;
|
||||
|
||||
private class Mapping : IntrusiveRedBlackTreeNode<Mapping>, IComparable<Mapping>, IComparable<ulong>
|
||||
{
|
||||
public ulong Address { get; }
|
||||
public ulong Size { get; }
|
||||
public ulong EndAddress => Address + Size;
|
||||
public ulong Va { get; }
|
||||
public ulong EndVa { get; }
|
||||
|
||||
public Mapping(ulong address, ulong size, ulong va, ulong endVa)
|
||||
{
|
||||
Address = address;
|
||||
Size = size;
|
||||
Va = va;
|
||||
EndVa = endVa;
|
||||
}
|
||||
|
||||
public int CompareTo(Mapping other)
|
||||
{
|
||||
if (Address < other.Address)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (Address <= other.EndAddress - 1UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
public int CompareTo(ulong address)
|
||||
{
|
||||
if (address < Address)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (address <= EndAddress - 1UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly AddressIntrusiveRedBlackTree<Mapping> _mappingTree;
|
||||
private readonly object _lock;
|
||||
|
||||
public Block(MemoryTracking tracking, Func<ulong, ulong> readPtCallback, MemoryBlock memory, ulong size, object locker) : base(memory, size)
|
||||
{
|
||||
_tracking = tracking;
|
||||
_readPtCallback = readPtCallback;
|
||||
_memoryEh = new(memory, null, tracking, VirtualMemoryEvent);
|
||||
_mappingTree = new();
|
||||
_lock = locker;
|
||||
}
|
||||
|
||||
public void AddMapping(ulong offset, ulong size, ulong va, ulong endVa)
|
||||
{
|
||||
_mappingTree.Add(new(offset, size, va, endVa));
|
||||
}
|
||||
|
||||
public void RemoveMapping(ulong offset, ulong size)
|
||||
{
|
||||
_mappingTree.Remove(_mappingTree.GetNode(offset));
|
||||
}
|
||||
|
||||
private ulong VirtualMemoryEvent(ulong address, ulong size, bool write)
|
||||
{
|
||||
Mapping map;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
map = _mappingTree.GetNode(address);
|
||||
}
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
address -= map.Address;
|
||||
|
||||
ulong addressAligned = BitUtils.AlignDown(address, AddressSpacePartition.GuestPageSize);
|
||||
ulong endAddressAligned = BitUtils.AlignUp(address + size, AddressSpacePartition.GuestPageSize);
|
||||
ulong sizeAligned = endAddressAligned - addressAligned;
|
||||
|
||||
if (!_tracking.VirtualMemoryEvent(map.Va + addressAligned, sizeAligned, write))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return _readPtCallback(map.Va + address);
|
||||
}
|
||||
|
||||
public override void Destroy()
|
||||
{
|
||||
_memoryEh.Dispose();
|
||||
|
||||
base.Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly MemoryTracking _tracking;
|
||||
private readonly Func<ulong, ulong> _readPtCallback;
|
||||
private readonly object _lock;
|
||||
|
||||
public AddressSpacePartitionAllocator(
|
||||
MemoryTracking tracking,
|
||||
Func<ulong, ulong> readPtCallback,
|
||||
object locker) : base(DefaultBlockAlignment, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible)
|
||||
{
|
||||
_tracking = tracking;
|
||||
_readPtCallback = readPtCallback;
|
||||
_lock = locker;
|
||||
}
|
||||
|
||||
public AddressSpacePartitionAllocation Allocate(ulong va, ulong size)
|
||||
{
|
||||
AddressSpacePartitionAllocation allocation = new(this, Allocate(size, MemoryBlock.GetPageSize(), CreateBlock));
|
||||
allocation.RegisterMapping(va, va + size);
|
||||
|
||||
return allocation;
|
||||
}
|
||||
|
||||
private Block CreateBlock(MemoryBlock memory, ulong size)
|
||||
{
|
||||
return new Block(_tracking, _readPtCallback, memory, size, _lock);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit.HostTracked
|
||||
{
|
||||
class AddressSpacePartitionMultiAllocation : IDisposable
|
||||
{
|
||||
private readonly AddressSpacePartitionAllocation _baseMemory;
|
||||
private AddressSpacePartitionAllocation _baseMemoryRo;
|
||||
private AddressSpacePartitionAllocation _baseMemoryNone;
|
||||
|
||||
public AddressSpacePartitionMultiAllocation(AddressSpacePartitionAllocation baseMemory)
|
||||
{
|
||||
_baseMemory = baseMemory;
|
||||
}
|
||||
|
||||
public void MapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size)
|
||||
{
|
||||
_baseMemory.MapView(srcBlock, srcOffset, dstOffset, size);
|
||||
|
||||
if (_baseMemoryRo.IsValid)
|
||||
{
|
||||
_baseMemoryRo.MapView(srcBlock, srcOffset, dstOffset, size);
|
||||
_baseMemoryRo.Reprotect(dstOffset, size, MemoryPermission.Read, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void LateMapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size)
|
||||
{
|
||||
_baseMemoryRo.MapView(srcBlock, srcOffset, dstOffset, size);
|
||||
_baseMemoryRo.Reprotect(dstOffset, size, MemoryPermission.Read, false);
|
||||
}
|
||||
|
||||
public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size)
|
||||
{
|
||||
_baseMemory.UnmapView(srcBlock, offset, size);
|
||||
|
||||
if (_baseMemoryRo.IsValid)
|
||||
{
|
||||
_baseMemoryRo.UnmapView(srcBlock, offset, size);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail)
|
||||
{
|
||||
_baseMemory.Reprotect(offset, size, permission, throwOnFail);
|
||||
}
|
||||
|
||||
public IntPtr GetPointer(ulong offset, ulong size)
|
||||
{
|
||||
return _baseMemory.GetPointer(offset, size);
|
||||
}
|
||||
|
||||
public bool LazyInitMirrorForProtection(AddressSpacePartitioned addressSpace, ulong blockAddress, ulong blockSize, MemoryPermission permission)
|
||||
{
|
||||
if (permission == MemoryPermission.None && !_baseMemoryNone.IsValid)
|
||||
{
|
||||
_baseMemoryNone = addressSpace.CreateAsPartitionAllocation(blockAddress, blockSize);
|
||||
}
|
||||
else if (permission == MemoryPermission.Read && !_baseMemoryRo.IsValid)
|
||||
{
|
||||
_baseMemoryRo = addressSpace.CreateAsPartitionAllocation(blockAddress, blockSize);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IntPtr GetPointerForProtection(ulong offset, ulong size, MemoryPermission permission)
|
||||
{
|
||||
AddressSpacePartitionAllocation allocation = permission switch
|
||||
{
|
||||
MemoryPermission.ReadAndWrite => _baseMemory,
|
||||
MemoryPermission.Read => _baseMemoryRo,
|
||||
MemoryPermission.None => _baseMemoryNone,
|
||||
_ => throw new ArgumentException($"Invalid protection \"{permission}\"."),
|
||||
};
|
||||
|
||||
Debug.Assert(allocation.IsValid);
|
||||
|
||||
return allocation.GetPointer(offset, size);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_baseMemory.Dispose();
|
||||
|
||||
if (_baseMemoryRo.IsValid)
|
||||
{
|
||||
_baseMemoryRo.Dispose();
|
||||
}
|
||||
|
||||
if (_baseMemoryNone.IsValid)
|
||||
{
|
||||
_baseMemoryNone.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
407
src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitioned.cs
Normal file
407
src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitioned.cs
Normal file
|
@ -0,0 +1,407 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit.HostTracked
|
||||
{
|
||||
class AddressSpacePartitioned : IDisposable
|
||||
{
|
||||
private const int PartitionBits = 25;
|
||||
private const ulong PartitionSize = 1UL << PartitionBits;
|
||||
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly List<AddressSpacePartition> _partitions;
|
||||
private readonly AddressSpacePartitionAllocator _asAllocator;
|
||||
private readonly Action<ulong, IntPtr, ulong> _updatePtCallback;
|
||||
private readonly bool _useProtectionMirrors;
|
||||
|
||||
public AddressSpacePartitioned(MemoryTracking tracking, MemoryBlock backingMemory, NativePageTable nativePageTable, bool useProtectionMirrors)
|
||||
{
|
||||
_backingMemory = backingMemory;
|
||||
_partitions = new();
|
||||
_asAllocator = new(tracking, nativePageTable.Read, _partitions);
|
||||
_updatePtCallback = nativePageTable.Update;
|
||||
_useProtectionMirrors = useProtectionMirrors;
|
||||
}
|
||||
|
||||
public void Map(ulong va, ulong pa, ulong size)
|
||||
{
|
||||
ulong endVa = va + size;
|
||||
|
||||
lock (_partitions)
|
||||
{
|
||||
EnsurePartitionsLocked(va, size);
|
||||
|
||||
while (va < endVa)
|
||||
{
|
||||
int partitionIndex = FindPartitionIndexLocked(va);
|
||||
AddressSpacePartition partition = _partitions[partitionIndex];
|
||||
|
||||
(ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa);
|
||||
|
||||
partition.Map(clampedVa, pa, clampedEndVa - clampedVa);
|
||||
|
||||
ulong currentSize = clampedEndVa - clampedVa;
|
||||
|
||||
va += currentSize;
|
||||
pa += currentSize;
|
||||
|
||||
InsertOrRemoveBridgeIfNeeded(partitionIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
ulong endVa = va + size;
|
||||
|
||||
while (va < endVa)
|
||||
{
|
||||
AddressSpacePartition partition;
|
||||
|
||||
lock (_partitions)
|
||||
{
|
||||
int partitionIndex = FindPartitionIndexLocked(va);
|
||||
if (partitionIndex < 0)
|
||||
{
|
||||
va += PartitionSize - (va & (PartitionSize - 1));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
partition = _partitions[partitionIndex];
|
||||
|
||||
(ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa);
|
||||
|
||||
partition.Unmap(clampedVa, clampedEndVa - clampedVa);
|
||||
|
||||
va += clampedEndVa - clampedVa;
|
||||
|
||||
InsertOrRemoveBridgeIfNeeded(partitionIndex);
|
||||
|
||||
if (partition.IsEmpty())
|
||||
{
|
||||
_partitions.Remove(partition);
|
||||
partition.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
|
||||
{
|
||||
ulong endVa = va + size;
|
||||
|
||||
lock (_partitions)
|
||||
{
|
||||
while (va < endVa)
|
||||
{
|
||||
AddressSpacePartition partition = FindPartitionWithIndex(va, out int partitionIndex);
|
||||
|
||||
if (partition == null)
|
||||
{
|
||||
va += PartitionSize - (va & (PartitionSize - 1));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
(ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa);
|
||||
|
||||
if (_useProtectionMirrors)
|
||||
{
|
||||
partition.Reprotect(clampedVa, clampedEndVa - clampedVa, protection, this, _updatePtCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
partition.ReprotectAligned(clampedVa, clampedEndVa - clampedVa, protection);
|
||||
|
||||
if (clampedVa == partition.Address &&
|
||||
partitionIndex > 0 &&
|
||||
_partitions[partitionIndex - 1].EndAddress == partition.Address)
|
||||
{
|
||||
_partitions[partitionIndex - 1].ReprotectBridge(protection);
|
||||
}
|
||||
}
|
||||
|
||||
va += clampedEndVa - clampedVa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PrivateRange GetPrivateAllocation(ulong va)
|
||||
{
|
||||
AddressSpacePartition partition = FindPartition(va);
|
||||
|
||||
if (partition == null)
|
||||
{
|
||||
return PrivateRange.Empty;
|
||||
}
|
||||
|
||||
return partition.GetPrivateAllocation(va);
|
||||
}
|
||||
|
||||
public PrivateRange GetFirstPrivateAllocation(ulong va, ulong size, out ulong nextVa)
|
||||
{
|
||||
AddressSpacePartition partition = FindPartition(va);
|
||||
|
||||
if (partition == null)
|
||||
{
|
||||
nextVa = (va & ~(PartitionSize - 1)) + PartitionSize;
|
||||
|
||||
return PrivateRange.Empty;
|
||||
}
|
||||
|
||||
return partition.GetFirstPrivateAllocation(va, size, out nextVa);
|
||||
}
|
||||
|
||||
public bool HasAnyPrivateAllocation(ulong va, ulong size, out PrivateRange range)
|
||||
{
|
||||
range = PrivateRange.Empty;
|
||||
|
||||
ulong startVa = va;
|
||||
ulong endVa = va + size;
|
||||
|
||||
while (va < endVa)
|
||||
{
|
||||
AddressSpacePartition partition = FindPartition(va);
|
||||
|
||||
if (partition == null)
|
||||
{
|
||||
va += PartitionSize - (va & (PartitionSize - 1));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
(ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa);
|
||||
|
||||
if (partition.HasPrivateAllocation(clampedVa, clampedEndVa - clampedVa, startVa, size, ref range))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
va += clampedEndVa - clampedVa;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void InsertOrRemoveBridgeIfNeeded(int partitionIndex)
|
||||
{
|
||||
if (partitionIndex > 0)
|
||||
{
|
||||
if (_partitions[partitionIndex - 1].EndAddress == _partitions[partitionIndex].Address)
|
||||
{
|
||||
_partitions[partitionIndex - 1].InsertBridgeAtEnd(_partitions[partitionIndex], _useProtectionMirrors);
|
||||
}
|
||||
else
|
||||
{
|
||||
_partitions[partitionIndex - 1].InsertBridgeAtEnd(null, _useProtectionMirrors);
|
||||
}
|
||||
}
|
||||
|
||||
if (partitionIndex + 1 < _partitions.Count && _partitions[partitionIndex].EndAddress == _partitions[partitionIndex + 1].Address)
|
||||
{
|
||||
_partitions[partitionIndex].InsertBridgeAtEnd(_partitions[partitionIndex + 1], _useProtectionMirrors);
|
||||
}
|
||||
else
|
||||
{
|
||||
_partitions[partitionIndex].InsertBridgeAtEnd(null, _useProtectionMirrors);
|
||||
}
|
||||
}
|
||||
|
||||
public IntPtr GetPointer(ulong va, ulong size)
|
||||
{
|
||||
AddressSpacePartition partition = FindPartition(va);
|
||||
|
||||
return partition.GetPointer(va, size);
|
||||
}
|
||||
|
||||
private static (ulong, ulong) ClampRange(AddressSpacePartition partition, ulong va, ulong endVa)
|
||||
{
|
||||
if (va < partition.Address)
|
||||
{
|
||||
va = partition.Address;
|
||||
}
|
||||
|
||||
if (endVa > partition.EndAddress)
|
||||
{
|
||||
endVa = partition.EndAddress;
|
||||
}
|
||||
|
||||
return (va, endVa);
|
||||
}
|
||||
|
||||
private AddressSpacePartition FindPartition(ulong va)
|
||||
{
|
||||
lock (_partitions)
|
||||
{
|
||||
int index = FindPartitionIndexLocked(va);
|
||||
if (index >= 0)
|
||||
{
|
||||
return _partitions[index];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private AddressSpacePartition FindPartitionWithIndex(ulong va, out int index)
|
||||
{
|
||||
lock (_partitions)
|
||||
{
|
||||
index = FindPartitionIndexLocked(va);
|
||||
if (index >= 0)
|
||||
{
|
||||
return _partitions[index];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private int FindPartitionIndexLocked(ulong va)
|
||||
{
|
||||
int left = 0;
|
||||
int middle;
|
||||
int right = _partitions.Count - 1;
|
||||
|
||||
while (left <= right)
|
||||
{
|
||||
middle = left + ((right - left) >> 1);
|
||||
|
||||
AddressSpacePartition partition = _partitions[middle];
|
||||
|
||||
if (partition.Address <= va && partition.EndAddress > va)
|
||||
{
|
||||
return middle;
|
||||
}
|
||||
|
||||
if (partition.Address >= va)
|
||||
{
|
||||
right = middle - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void EnsurePartitionsLocked(ulong va, ulong size)
|
||||
{
|
||||
ulong endVa = BitUtils.AlignUp(va + size, PartitionSize);
|
||||
va = BitUtils.AlignDown(va, PartitionSize);
|
||||
|
||||
for (int i = 0; i < _partitions.Count && va < endVa; i++)
|
||||
{
|
||||
AddressSpacePartition partition = _partitions[i];
|
||||
|
||||
if (partition.Address <= va && partition.EndAddress > va)
|
||||
{
|
||||
if (partition.EndAddress >= endVa)
|
||||
{
|
||||
// Fully mapped already.
|
||||
va = endVa;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
ulong gapSize;
|
||||
|
||||
if (i + 1 < _partitions.Count)
|
||||
{
|
||||
AddressSpacePartition nextPartition = _partitions[i + 1];
|
||||
|
||||
if (partition.EndAddress == nextPartition.Address)
|
||||
{
|
||||
va = partition.EndAddress;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
gapSize = Math.Min(endVa, nextPartition.Address) - partition.EndAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
gapSize = endVa - partition.EndAddress;
|
||||
}
|
||||
|
||||
_partitions.Insert(i + 1, CreateAsPartition(partition.EndAddress, gapSize));
|
||||
va = partition.EndAddress + gapSize;
|
||||
i++;
|
||||
}
|
||||
else if (partition.EndAddress > va)
|
||||
{
|
||||
Debug.Assert(partition.Address > va);
|
||||
|
||||
ulong gapSize;
|
||||
|
||||
if (partition.Address < endVa)
|
||||
{
|
||||
gapSize = partition.Address - va;
|
||||
}
|
||||
else
|
||||
{
|
||||
gapSize = endVa - va;
|
||||
}
|
||||
|
||||
_partitions.Insert(i, CreateAsPartition(va, gapSize));
|
||||
va = Math.Min(partition.EndAddress, endVa);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (va < endVa)
|
||||
{
|
||||
_partitions.Add(CreateAsPartition(va, endVa - va));
|
||||
}
|
||||
|
||||
ValidatePartitionList();
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void ValidatePartitionList()
|
||||
{
|
||||
for (int i = 1; i < _partitions.Count; i++)
|
||||
{
|
||||
Debug.Assert(_partitions[i].Address > _partitions[i - 1].Address);
|
||||
Debug.Assert(_partitions[i].EndAddress > _partitions[i - 1].EndAddress);
|
||||
}
|
||||
}
|
||||
|
||||
private AddressSpacePartition CreateAsPartition(ulong va, ulong size)
|
||||
{
|
||||
return new(CreateAsPartitionAllocation(va, size), _backingMemory, va, size);
|
||||
}
|
||||
|
||||
public AddressSpacePartitionAllocation CreateAsPartitionAllocation(ulong va, ulong size)
|
||||
{
|
||||
return _asAllocator.Allocate(va, size + MemoryBlock.GetPageSize());
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
foreach (AddressSpacePartition partition in _partitions)
|
||||
{
|
||||
partition.Dispose();
|
||||
}
|
||||
|
||||
_partitions.Clear();
|
||||
_asAllocator.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
223
src/Ryujinx.Cpu/Jit/HostTracked/NativePageTable.cs
Normal file
223
src/Ryujinx.Cpu/Jit/HostTracked/NativePageTable.cs
Normal file
|
@ -0,0 +1,223 @@
|
|||
using Ryujinx.Cpu.Signal;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit.HostTracked
|
||||
{
|
||||
sealed class NativePageTable : IDisposable
|
||||
{
|
||||
private delegate ulong TrackingEventDelegate(ulong address, ulong size, bool write);
|
||||
|
||||
private const int PageBits = 12;
|
||||
private const int PageSize = 1 << PageBits;
|
||||
private const int PageMask = PageSize - 1;
|
||||
|
||||
private const int PteSize = 8;
|
||||
|
||||
private readonly int _bitsPerPtPage;
|
||||
private readonly int _entriesPerPtPage;
|
||||
private readonly int _pageCommitmentBits;
|
||||
|
||||
private readonly PageTable<ulong> _pageTable;
|
||||
private readonly MemoryBlock _nativePageTable;
|
||||
private readonly ulong[] _pageCommitmentBitmap;
|
||||
private readonly ulong _hostPageSize;
|
||||
|
||||
private readonly TrackingEventDelegate _trackingEvent;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public IntPtr PageTablePointer => _nativePageTable.Pointer;
|
||||
|
||||
public NativePageTable(ulong asSize)
|
||||
{
|
||||
ulong hostPageSize = MemoryBlock.GetPageSize();
|
||||
|
||||
_entriesPerPtPage = (int)(hostPageSize / sizeof(ulong));
|
||||
_bitsPerPtPage = BitOperations.Log2((uint)_entriesPerPtPage);
|
||||
_pageCommitmentBits = PageBits + _bitsPerPtPage;
|
||||
|
||||
_hostPageSize = hostPageSize;
|
||||
_pageTable = new PageTable<ulong>();
|
||||
_nativePageTable = new MemoryBlock((asSize / PageSize) * PteSize + _hostPageSize, MemoryAllocationFlags.Reserve);
|
||||
_pageCommitmentBitmap = new ulong[(asSize >> _pageCommitmentBits) / (sizeof(ulong) * 8)];
|
||||
|
||||
ulong ptStart = (ulong)_nativePageTable.Pointer;
|
||||
ulong ptEnd = ptStart + _nativePageTable.Size;
|
||||
|
||||
_trackingEvent = VirtualMemoryEvent;
|
||||
|
||||
bool added = NativeSignalHandler.AddTrackedRegion((nuint)ptStart, (nuint)ptEnd, Marshal.GetFunctionPointerForDelegate(_trackingEvent));
|
||||
|
||||
if (!added)
|
||||
{
|
||||
throw new InvalidOperationException("Number of allowed tracked regions exceeded.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Map(ulong va, ulong pa, ulong size, AddressSpacePartitioned addressSpace, MemoryBlock backingMemory, bool privateMap)
|
||||
{
|
||||
while (size != 0)
|
||||
{
|
||||
_pageTable.Map(va, pa);
|
||||
|
||||
EnsureCommitment(va);
|
||||
|
||||
if (privateMap)
|
||||
{
|
||||
_nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, addressSpace.GetPointer(va, PageSize)));
|
||||
}
|
||||
else
|
||||
{
|
||||
_nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, backingMemory.GetPointer(pa, PageSize)));
|
||||
}
|
||||
|
||||
va += PageSize;
|
||||
pa += PageSize;
|
||||
size -= PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
IntPtr guardPagePtr = GetGuardPagePointer();
|
||||
|
||||
while (size != 0)
|
||||
{
|
||||
_pageTable.Unmap(va);
|
||||
_nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, guardPagePtr));
|
||||
|
||||
va += PageSize;
|
||||
size -= PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
public ulong Read(ulong va)
|
||||
{
|
||||
ulong pte = _nativePageTable.Read<ulong>((va / PageSize) * PteSize);
|
||||
|
||||
pte += va & ~(ulong)PageMask;
|
||||
|
||||
return pte + (va & PageMask);
|
||||
}
|
||||
|
||||
public void Update(ulong va, IntPtr ptr, ulong size)
|
||||
{
|
||||
ulong remainingSize = size;
|
||||
|
||||
while (remainingSize != 0)
|
||||
{
|
||||
EnsureCommitment(va);
|
||||
|
||||
_nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, ptr));
|
||||
|
||||
va += PageSize;
|
||||
ptr += PageSize;
|
||||
remainingSize -= PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureCommitment(ulong va)
|
||||
{
|
||||
ulong bit = va >> _pageCommitmentBits;
|
||||
|
||||
int index = (int)(bit / (sizeof(ulong) * 8));
|
||||
int shift = (int)(bit % (sizeof(ulong) * 8));
|
||||
|
||||
ulong mask = 1UL << shift;
|
||||
|
||||
ulong oldMask = _pageCommitmentBitmap[index];
|
||||
|
||||
if ((oldMask & mask) == 0)
|
||||
{
|
||||
lock (_pageCommitmentBitmap)
|
||||
{
|
||||
oldMask = _pageCommitmentBitmap[index];
|
||||
|
||||
if ((oldMask & mask) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_nativePageTable.Commit(bit * _hostPageSize, _hostPageSize);
|
||||
|
||||
Span<ulong> pageSpan = MemoryMarshal.Cast<byte, ulong>(_nativePageTable.GetSpan(bit * _hostPageSize, (int)_hostPageSize));
|
||||
|
||||
Debug.Assert(pageSpan.Length == _entriesPerPtPage);
|
||||
|
||||
IntPtr guardPagePtr = GetGuardPagePointer();
|
||||
|
||||
for (int i = 0; i < pageSpan.Length; i++)
|
||||
{
|
||||
pageSpan[i] = GetPte((bit << _pageCommitmentBits) | ((ulong)i * PageSize), guardPagePtr);
|
||||
}
|
||||
|
||||
_pageCommitmentBitmap[index] = oldMask | mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr GetGuardPagePointer()
|
||||
{
|
||||
return _nativePageTable.GetPointer(_nativePageTable.Size - _hostPageSize, _hostPageSize);
|
||||
}
|
||||
|
||||
private static ulong GetPte(ulong va, IntPtr ptr)
|
||||
{
|
||||
Debug.Assert((va & PageMask) == 0);
|
||||
|
||||
return (ulong)ptr - va;
|
||||
}
|
||||
|
||||
public ulong GetPhysicalAddress(ulong va)
|
||||
{
|
||||
return _pageTable.Read(va) + (va & PageMask);
|
||||
}
|
||||
|
||||
private ulong VirtualMemoryEvent(ulong address, ulong size, bool write)
|
||||
{
|
||||
if (address < _nativePageTable.Size - _hostPageSize)
|
||||
{
|
||||
// Some prefetch instructions do not cause faults with invalid addresses.
|
||||
// Retry if we are hitting a case where the page table is unmapped, the next
|
||||
// run will execute the actual instruction.
|
||||
// The address loaded from the page table will be invalid, and it should hit the else case
|
||||
// if the instruction faults on unmapped or protected memory.
|
||||
|
||||
ulong va = address * (PageSize / sizeof(ulong));
|
||||
|
||||
EnsureCommitment(va);
|
||||
|
||||
return (ulong)_nativePageTable.Pointer + address;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidMemoryRegionException();
|
||||
}
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
NativeSignalHandler.RemoveTrackedRegion((nuint)_nativePageTable.Pointer);
|
||||
|
||||
_nativePageTable.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,9 +15,9 @@ namespace Ryujinx.Cpu.Jit
|
|||
_tickSource = tickSource;
|
||||
_translator = new Translator(new JitMemoryAllocator(forJit: true), memory, for64Bit);
|
||||
|
||||
if (memory.Type.IsHostMapped())
|
||||
if (memory.Type.IsHostMappedOrTracked())
|
||||
{
|
||||
NativeSignalHandler.InitializeSignalHandler(MemoryBlock.GetPageSize());
|
||||
NativeSignalHandler.InitializeSignalHandler();
|
||||
}
|
||||
|
||||
memory.UnmapEvent += UnmapHandler;
|
||||
|
|
|
@ -3,6 +3,7 @@ using Ryujinx.Memory;
|
|||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
@ -14,7 +15,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
/// <summary>
|
||||
/// Represents a CPU memory manager.
|
||||
/// </summary>
|
||||
public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked
|
||||
{
|
||||
private const int PteSize = 8;
|
||||
|
||||
|
@ -24,7 +25,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Supports4KBPages => true;
|
||||
public bool UsesPrivateAllocations => false;
|
||||
|
||||
/// <summary>
|
||||
/// Address space width in bits.
|
||||
|
@ -33,6 +34,8 @@ namespace Ryujinx.Cpu.Jit
|
|||
|
||||
private readonly MemoryBlock _pageTable;
|
||||
|
||||
private readonly ManagedPageFlags _pages;
|
||||
|
||||
/// <summary>
|
||||
/// Page table base pointer.
|
||||
/// </summary>
|
||||
|
@ -70,6 +73,8 @@ namespace Ryujinx.Cpu.Jit
|
|||
AddressSpaceSize = asSize;
|
||||
_pageTable = new MemoryBlock((asSize / PageSize) * PteSize);
|
||||
|
||||
_pages = new ManagedPageFlags(AddressSpaceBits);
|
||||
|
||||
Tracking = new MemoryTracking(this, PageSize);
|
||||
}
|
||||
|
||||
|
@ -89,15 +94,10 @@ namespace Ryujinx.Cpu.Jit
|
|||
remainingSize -= PageSize;
|
||||
}
|
||||
|
||||
_pages.AddMapping(oVa, size);
|
||||
Tracking.Map(oVa, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void MapForeign(ulong va, nuint hostPointer, ulong size)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
|
@ -111,6 +111,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
|
||||
UnmapEvent?.Invoke(va, size);
|
||||
Tracking.Unmap(va, size);
|
||||
_pages.RemoveMapping(va, size);
|
||||
|
||||
ulong remainingSize = size;
|
||||
while (remainingSize != 0)
|
||||
|
@ -122,18 +123,29 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T Read<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T ReadTracked<T>(ulong va) where T : unmanaged
|
||||
public override T ReadTracked<T>(ulong va)
|
||||
{
|
||||
try
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
|
||||
return base.ReadTracked<T>(va);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T ReadGuest<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
try
|
||||
{
|
||||
SignalMemoryTrackingImpl(va, (ulong)Unsafe.SizeOf<T>(), false, true);
|
||||
|
||||
return Read<T>(va);
|
||||
}
|
||||
|
@ -164,107 +176,11 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Write<T>(ulong va, T value) where T : unmanaged
|
||||
{
|
||||
Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Write(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SignalMemoryTracking(va, (ulong)data.Length, true);
|
||||
|
||||
WriteImpl(va, data);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WriteImpl(va, data);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SignalMemoryTracking(va, (ulong)data.Length, false);
|
||||
|
||||
if (IsContiguousAndMapped(va, data.Length))
|
||||
{
|
||||
var target = _backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length);
|
||||
|
||||
bool changed = !data.SequenceEqual(target);
|
||||
|
||||
if (changed)
|
||||
{
|
||||
data.CopyTo(target);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteImpl(va, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to CPU mapped memory.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address to write the data into</param>
|
||||
/// <param name="data">Data to be written</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void WriteImpl(ulong va, ReadOnlySpan<byte> data)
|
||||
public override void Write(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
try
|
||||
{
|
||||
AssertValidAddressAndSize(va, (ulong)data.Length);
|
||||
|
||||
if (IsContiguousAndMapped(va, data.Length))
|
||||
{
|
||||
data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
|
||||
}
|
||||
else
|
||||
{
|
||||
int offset = 0, size;
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va);
|
||||
|
||||
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
||||
|
||||
data[..size].CopyTo(_backingMemory.GetSpan(pa, size));
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, PageSize);
|
||||
|
||||
data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size));
|
||||
}
|
||||
}
|
||||
base.Write(va, data);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
|
@ -276,60 +192,47 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
|
||||
public void WriteGuest<T>(ulong va, T value) where T : unmanaged
|
||||
{
|
||||
if (size == 0)
|
||||
Span<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1));
|
||||
|
||||
SignalMemoryTrackingImpl(va, (ulong)data.Length, true, true);
|
||||
|
||||
Write(va, data);
|
||||
}
|
||||
|
||||
public override void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ReadOnlySpan<byte>.Empty;
|
||||
base.WriteUntracked(va, data);
|
||||
}
|
||||
|
||||
if (tracked)
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)size, false);
|
||||
}
|
||||
|
||||
if (IsContiguousAndMapped(va, size))
|
||||
{
|
||||
return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> data = new byte[size];
|
||||
|
||||
base.Read(va, data);
|
||||
|
||||
return data;
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
|
||||
public override ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false)
|
||||
{
|
||||
if (size == 0)
|
||||
try
|
||||
{
|
||||
return new WritableRegion(null, va, Memory<byte>.Empty);
|
||||
return base.GetReadOnlySequence(va, size, tracked);
|
||||
}
|
||||
|
||||
if (IsContiguousAndMapped(va, size))
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
if (tracked)
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)size, true);
|
||||
throw;
|
||||
}
|
||||
|
||||
return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
|
||||
}
|
||||
else
|
||||
{
|
||||
Memory<byte> memory = new byte[size];
|
||||
|
||||
GetSpan(va, size).CopyTo(memory.Span);
|
||||
|
||||
return new WritableRegion(this, va, memory, tracked);
|
||||
return ReadOnlySequence<byte>.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ref T GetRef<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
if (!IsContiguous(va, Unsafe.SizeOf<T>()))
|
||||
|
@ -342,56 +245,6 @@ namespace Ryujinx.Cpu.Jit
|
|||
return ref _backingMemory.GetRef<T>(GetPhysicalAddressInternal(va));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of pages in a virtual address range.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range</param>
|
||||
/// <param name="startVa">The virtual address of the beginning of the first page</param>
|
||||
/// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetPagesCount(ulong va, uint size, out ulong startVa)
|
||||
{
|
||||
// WARNING: Always check if ulong does not overflow during the operations.
|
||||
startVa = va & ~(ulong)PageMask;
|
||||
ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
|
||||
|
||||
return (int)(vaSpan / PageSize);
|
||||
}
|
||||
|
||||
private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsContiguous(ulong va, int size)
|
||||
{
|
||||
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int pages = GetPagesCount(va, (uint)size, out va);
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
if (!ValidateAddress(va + PageSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
va += PageSize;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
|
||||
{
|
||||
|
@ -496,9 +349,8 @@ namespace Ryujinx.Cpu.Jit
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsMapped(ulong va)
|
||||
public override bool IsMapped(ulong va)
|
||||
{
|
||||
if (!ValidateAddress(va))
|
||||
{
|
||||
|
@ -508,9 +360,9 @@ namespace Ryujinx.Cpu.Jit
|
|||
return _pageTable.Read<ulong>((va / PageSize) * PteSize) != 0;
|
||||
}
|
||||
|
||||
private ulong GetPhysicalAddressInternal(ulong va)
|
||||
private nuint GetPhysicalAddressInternal(ulong va)
|
||||
{
|
||||
return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
|
||||
return (nuint)(PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -520,50 +372,57 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
// Protection is inverted on software pages, since the default value is 0.
|
||||
protection = (~protection) & MemoryPermission.ReadAndWrite;
|
||||
|
||||
long tag = protection switch
|
||||
if (guest)
|
||||
{
|
||||
MemoryPermission.None => 0L,
|
||||
MemoryPermission.Write => 2L << PointerTagBit,
|
||||
_ => 3L << PointerTagBit,
|
||||
};
|
||||
// Protection is inverted on software pages, since the default value is 0.
|
||||
protection = (~protection) & MemoryPermission.ReadAndWrite;
|
||||
|
||||
int pages = GetPagesCount(va, (uint)size, out va);
|
||||
ulong pageStart = va >> PageBits;
|
||||
long invTagMask = ~(0xffffL << 48);
|
||||
|
||||
for (int page = 0; page < pages; page++)
|
||||
{
|
||||
ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
|
||||
|
||||
long pte;
|
||||
|
||||
do
|
||||
long tag = protection switch
|
||||
{
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
}
|
||||
while (pte != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
|
||||
MemoryPermission.None => 0L,
|
||||
MemoryPermission.Write => 2L << PointerTagBit,
|
||||
_ => 3L << PointerTagBit,
|
||||
};
|
||||
|
||||
pageStart++;
|
||||
int pages = GetPagesCount(va, (uint)size, out va);
|
||||
ulong pageStart = va >> PageBits;
|
||||
long invTagMask = ~(0xffffL << 48);
|
||||
|
||||
for (int page = 0; page < pages; page++)
|
||||
{
|
||||
ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
|
||||
|
||||
long pte;
|
||||
|
||||
do
|
||||
{
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
}
|
||||
while (pte != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
|
||||
|
||||
pageStart++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_pages.TrackingReprotect(va, size, protection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RegionHandle BeginTracking(ulong address, ulong size, int id)
|
||||
public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None)
|
||||
{
|
||||
return Tracking.BeginTracking(address, size, id);
|
||||
return Tracking.BeginTracking(address, size, id, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
|
||||
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None)
|
||||
{
|
||||
return Tracking.BeginGranularTracking(address, size, handles, granularity, id);
|
||||
return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -572,8 +431,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
|
||||
private void SignalMemoryTrackingImpl(ulong va, ulong size, bool write, bool guest, bool precise = false, int? exemptId = null)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
|
@ -583,31 +441,45 @@ namespace Ryujinx.Cpu.Jit
|
|||
return;
|
||||
}
|
||||
|
||||
// We emulate guard pages for software memory access. This makes for an easy transition to
|
||||
// tracking using host guard pages in future, but also supporting platforms where this is not possible.
|
||||
// If the memory tracking is coming from the guest, use the tag bits in the page table entry.
|
||||
// Otherwise, use the managed page flags.
|
||||
|
||||
// Write tag includes read protection, since we don't have any read actions that aren't performed before write too.
|
||||
long tag = (write ? 3L : 1L) << PointerTagBit;
|
||||
|
||||
int pages = GetPagesCount(va, (uint)size, out _);
|
||||
ulong pageStart = va >> PageBits;
|
||||
|
||||
for (int page = 0; page < pages; page++)
|
||||
if (guest)
|
||||
{
|
||||
ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
|
||||
// We emulate guard pages for software memory access. This makes for an easy transition to
|
||||
// tracking using host guard pages in future, but also supporting platforms where this is not possible.
|
||||
|
||||
long pte;
|
||||
// Write tag includes read protection, since we don't have any read actions that aren't performed before write too.
|
||||
long tag = (write ? 3L : 1L) << PointerTagBit;
|
||||
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
int pages = GetPagesCount(va, (uint)size, out _);
|
||||
ulong pageStart = va >> PageBits;
|
||||
|
||||
if ((pte & tag) != 0)
|
||||
for (int page = 0; page < pages; page++)
|
||||
{
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
|
||||
break;
|
||||
}
|
||||
ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
|
||||
|
||||
pageStart++;
|
||||
long pte = Volatile.Read(ref pageRef);
|
||||
|
||||
if ((pte & tag) != 0)
|
||||
{
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId, true);
|
||||
break;
|
||||
}
|
||||
|
||||
pageStart++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
|
||||
{
|
||||
SignalMemoryTrackingImpl(va, size, write, false, precise, exemptId);
|
||||
}
|
||||
|
||||
private ulong PaToPte(ulong pa)
|
||||
|
@ -625,10 +497,16 @@ namespace Ryujinx.Cpu.Jit
|
|||
/// </summary>
|
||||
protected override void Destroy() => _pageTable.Dispose();
|
||||
|
||||
protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size)
|
||||
protected override Memory<byte> GetPhysicalAddressMemory(nuint pa, int size)
|
||||
=> _backingMemory.GetMemory(pa, size);
|
||||
|
||||
protected override Span<byte> GetPhysicalAddressSpan(nuint pa, int size)
|
||||
=> _backingMemory.GetSpan(pa, size);
|
||||
|
||||
protected override ulong TranslateVirtualAddressForRead(ulong va)
|
||||
protected override nuint TranslateVirtualAddressChecked(ulong va)
|
||||
=> GetPhysicalAddressInternal(va);
|
||||
|
||||
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
|
||||
=> GetPhysicalAddressInternal(va);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,33 +3,18 @@ using Ryujinx.Memory;
|
|||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
|
||||
/// </summary>
|
||||
public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked
|
||||
{
|
||||
public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry.
|
||||
public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set.
|
||||
|
||||
private enum HostMappedPtBits : ulong
|
||||
{
|
||||
Unmapped = 0,
|
||||
Mapped,
|
||||
WriteTracked,
|
||||
ReadWriteTracked,
|
||||
|
||||
MappedReplicated = 0x5555555555555555,
|
||||
WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa,
|
||||
ReadWriteTrackedReplicated = ulong.MaxValue,
|
||||
}
|
||||
|
||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||
private readonly bool _unsafeMode;
|
||||
|
||||
|
@ -39,10 +24,10 @@ namespace Ryujinx.Cpu.Jit
|
|||
|
||||
private readonly MemoryEhMeilleure _memoryEh;
|
||||
|
||||
private readonly ulong[] _pageBitmap;
|
||||
private readonly ManagedPageFlags _pages;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize;
|
||||
public bool UsesPrivateAllocations => false;
|
||||
|
||||
public int AddressSpaceBits { get; }
|
||||
|
||||
|
@ -81,7 +66,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
|
||||
AddressSpaceBits = asBits;
|
||||
|
||||
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
|
||||
_pages = new ManagedPageFlags(AddressSpaceBits);
|
||||
|
||||
Tracking = new MemoryTracking(this, (int)MemoryBlock.GetPageSize(), invalidAccessHandler);
|
||||
_memoryEh = new MemoryEhMeilleure(_addressSpace.Base, _addressSpace.Mirror, Tracking);
|
||||
|
@ -94,7 +79,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
/// <param name="size">Size of the range in bytes</param>
|
||||
private void AssertMapped(ulong va, ulong size)
|
||||
{
|
||||
if (!ValidateAddressAndSize(va, size) || !IsRangeMappedImpl(va, size))
|
||||
if (!ValidateAddressAndSize(va, size) || !_pages.IsRangeMapped(va, size))
|
||||
{
|
||||
throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
|
||||
}
|
||||
|
@ -106,18 +91,12 @@ namespace Ryujinx.Cpu.Jit
|
|||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
_addressSpace.Map(va, pa, size, flags);
|
||||
AddMapping(va, size);
|
||||
_pages.AddMapping(va, size);
|
||||
PtMap(va, pa, size);
|
||||
|
||||
Tracking.Map(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void MapForeign(ulong va, nuint hostPointer, ulong size)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
|
@ -126,7 +105,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
UnmapEvent?.Invoke(va, size);
|
||||
Tracking.Unmap(va, size);
|
||||
|
||||
RemoveMapping(va, size);
|
||||
_pages.RemoveMapping(va, size);
|
||||
PtUnmap(va, size);
|
||||
_addressSpace.Unmap(va, size);
|
||||
}
|
||||
|
@ -154,8 +133,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T Read<T>(ulong va) where T : unmanaged
|
||||
public override T Read<T>(ulong va)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -174,14 +152,11 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T ReadTracked<T>(ulong va) where T : unmanaged
|
||||
public override T ReadTracked<T>(ulong va)
|
||||
{
|
||||
try
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
|
||||
|
||||
return Read<T>(va);
|
||||
return base.ReadTracked<T>(va);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
|
@ -194,7 +169,6 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Read(ulong va, Span<byte> data)
|
||||
{
|
||||
try
|
||||
|
@ -212,9 +186,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Write<T>(ulong va, T value) where T : unmanaged
|
||||
public override void Write<T>(ulong va, T value)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -231,8 +203,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Write(ulong va, ReadOnlySpan<byte> data)
|
||||
public override void Write(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -249,8 +220,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
|
||||
public override void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -267,8 +237,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
|
||||
public override bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -295,8 +264,21 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
|
||||
public override ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false)
|
||||
{
|
||||
if (tracked)
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)size, write: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertMapped(va, (ulong)size);
|
||||
}
|
||||
|
||||
return new ReadOnlySequence<byte>(_addressSpace.Mirror.GetMemory(va, size));
|
||||
}
|
||||
|
||||
public override ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
|
||||
{
|
||||
if (tracked)
|
||||
{
|
||||
|
@ -310,8 +292,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
return _addressSpace.Mirror.GetSpan(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
|
||||
public override WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
|
||||
{
|
||||
if (tracked)
|
||||
{
|
||||
|
@ -325,7 +306,6 @@ namespace Ryujinx.Cpu.Jit
|
|||
return _addressSpace.Mirror.GetWritableRegion(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ref T GetRef<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
|
||||
|
@ -333,26 +313,10 @@ namespace Ryujinx.Cpu.Jit
|
|||
return ref _addressSpace.Mirror.GetRef<T>(va);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsMapped(ulong va)
|
||||
public override bool IsMapped(ulong va)
|
||||
{
|
||||
return ValidateAddress(va) && IsMappedImpl(va);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsMappedImpl(ulong va)
|
||||
{
|
||||
ulong page = va >> PageBits;
|
||||
|
||||
int bit = (int)((page & 31) << 1);
|
||||
|
||||
int pageIndex = (int)(page >> PageToPteShift);
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
|
||||
return ((pte >> bit) & 3) != 0;
|
||||
return ValidateAddress(va) && _pages.IsMapped(va);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -360,58 +324,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
return IsRangeMappedImpl(va, size);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex)
|
||||
{
|
||||
startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1);
|
||||
endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1));
|
||||
|
||||
pageIndex = (int)(pageStart >> PageToPteShift);
|
||||
pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift);
|
||||
}
|
||||
|
||||
private bool IsRangeMappedImpl(ulong va, ulong size)
|
||||
{
|
||||
int pages = GetPagesCount(va, size, out _);
|
||||
|
||||
if (pages == 1)
|
||||
{
|
||||
return IsMappedImpl(va);
|
||||
}
|
||||
|
||||
ulong pageStart = va >> PageBits;
|
||||
ulong pageEnd = pageStart + (ulong)pages;
|
||||
|
||||
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||
|
||||
// Check if either bit in each 2 bit page entry is set.
|
||||
// OR the block with itself shifted down by 1, and check the first bit of each entry.
|
||||
|
||||
ulong mask = BlockMappedMask & startMask;
|
||||
|
||||
while (pageIndex <= pageEndIndex)
|
||||
{
|
||||
if (pageIndex == pageEndIndex)
|
||||
{
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
|
||||
pte |= pte >> 1;
|
||||
if ((pte & mask) != mask)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
mask = BlockMappedMask;
|
||||
}
|
||||
|
||||
return true;
|
||||
return _pages.IsRangeMapped(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -472,11 +385,10 @@ namespace Ryujinx.Cpu.Jit
|
|||
return _pageTable.Read(va) + (va & PageMask);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
|
||||
/// </remarks>
|
||||
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
|
||||
public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
|
@ -486,93 +398,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
return;
|
||||
}
|
||||
|
||||
// Software table, used for managed memory tracking.
|
||||
|
||||
int pages = GetPagesCount(va, size, out _);
|
||||
ulong pageStart = va >> PageBits;
|
||||
|
||||
if (pages == 1)
|
||||
{
|
||||
ulong tag = (ulong)(write ? HostMappedPtBits.WriteTracked : HostMappedPtBits.ReadWriteTracked);
|
||||
|
||||
int bit = (int)((pageStart & 31) << 1);
|
||||
|
||||
int pageIndex = (int)(pageStart >> PageToPteShift);
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
ulong state = ((pte >> bit) & 3);
|
||||
|
||||
if (state >= tag)
|
||||
{
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
|
||||
return;
|
||||
}
|
||||
else if (state == 0)
|
||||
{
|
||||
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong pageEnd = pageStart + (ulong)pages;
|
||||
|
||||
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||
|
||||
ulong mask = startMask;
|
||||
|
||||
ulong anyTrackingTag = (ulong)HostMappedPtBits.WriteTrackedReplicated;
|
||||
|
||||
while (pageIndex <= pageEndIndex)
|
||||
{
|
||||
if (pageIndex == pageEndIndex)
|
||||
{
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
ulong mappedMask = mask & BlockMappedMask;
|
||||
|
||||
ulong mappedPte = pte | (pte >> 1);
|
||||
if ((mappedPte & mappedMask) != mappedMask)
|
||||
{
|
||||
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
|
||||
}
|
||||
|
||||
pte &= mask;
|
||||
if ((pte & anyTrackingTag) != 0) // Search for any tracking.
|
||||
{
|
||||
// Writes trigger any tracking.
|
||||
// Only trigger tracking from reads if both bits are set on any page.
|
||||
if (write || (pte & (pte >> 1) & BlockMappedMask) != 0)
|
||||
{
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mask = ulong.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of pages in a virtual address range.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range</param>
|
||||
/// <param name="startVa">The virtual address of the beginning of the first page</param>
|
||||
/// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetPagesCount(ulong va, ulong size, out ulong startVa)
|
||||
{
|
||||
// WARNING: Always check if ulong does not overflow during the operations.
|
||||
startVa = va & ~(ulong)PageMask;
|
||||
ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
|
||||
|
||||
return (int)(vaSpan / PageSize);
|
||||
_pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -582,103 +408,28 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest)
|
||||
{
|
||||
// Protection is inverted on software pages, since the default value is 0.
|
||||
protection = (~protection) & MemoryPermission.ReadAndWrite;
|
||||
|
||||
int pages = GetPagesCount(va, size, out va);
|
||||
ulong pageStart = va >> PageBits;
|
||||
|
||||
if (pages == 1)
|
||||
if (guest)
|
||||
{
|
||||
ulong protTag = protection switch
|
||||
{
|
||||
MemoryPermission.None => (ulong)HostMappedPtBits.Mapped,
|
||||
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTracked,
|
||||
_ => (ulong)HostMappedPtBits.ReadWriteTracked,
|
||||
};
|
||||
|
||||
int bit = (int)((pageStart & 31) << 1);
|
||||
|
||||
ulong tagMask = 3UL << bit;
|
||||
ulong invTagMask = ~tagMask;
|
||||
|
||||
ulong tag = protTag << bit;
|
||||
|
||||
int pageIndex = (int)(pageStart >> PageToPteShift);
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||
|
||||
ulong pte;
|
||||
|
||||
do
|
||||
{
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
}
|
||||
while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
|
||||
_addressSpace.Base.Reprotect(va, size, protection, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong pageEnd = pageStart + (ulong)pages;
|
||||
|
||||
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||
|
||||
ulong mask = startMask;
|
||||
|
||||
ulong protTag = protection switch
|
||||
{
|
||||
MemoryPermission.None => (ulong)HostMappedPtBits.MappedReplicated,
|
||||
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTrackedReplicated,
|
||||
_ => (ulong)HostMappedPtBits.ReadWriteTrackedReplicated,
|
||||
};
|
||||
|
||||
while (pageIndex <= pageEndIndex)
|
||||
{
|
||||
if (pageIndex == pageEndIndex)
|
||||
{
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
|
||||
ulong pte;
|
||||
ulong mappedMask;
|
||||
|
||||
// Change the protection of all 2 bit entries that are mapped.
|
||||
do
|
||||
{
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
|
||||
mappedMask = pte | (pte >> 1);
|
||||
mappedMask |= (mappedMask & BlockMappedMask) << 1;
|
||||
mappedMask &= mask; // Only update mapped pages within the given range.
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte);
|
||||
|
||||
mask = ulong.MaxValue;
|
||||
}
|
||||
_pages.TrackingReprotect(va, size, protection);
|
||||
}
|
||||
|
||||
protection = protection switch
|
||||
{
|
||||
MemoryPermission.None => MemoryPermission.ReadAndWrite,
|
||||
MemoryPermission.Write => MemoryPermission.Read,
|
||||
_ => MemoryPermission.None,
|
||||
};
|
||||
|
||||
_addressSpace.Base.Reprotect(va, size, protection, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RegionHandle BeginTracking(ulong address, ulong size, int id)
|
||||
public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None)
|
||||
{
|
||||
return Tracking.BeginTracking(address, size, id);
|
||||
return Tracking.BeginTracking(address, size, id, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
|
||||
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None)
|
||||
{
|
||||
return Tracking.BeginGranularTracking(address, size, handles, granularity, id);
|
||||
return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -687,86 +438,6 @@ namespace Ryujinx.Cpu.Jit
|
|||
return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given address mapping to the page table.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual memory address</param>
|
||||
/// <param name="size">Size to be mapped</param>
|
||||
private void AddMapping(ulong va, ulong size)
|
||||
{
|
||||
int pages = GetPagesCount(va, size, out _);
|
||||
ulong pageStart = va >> PageBits;
|
||||
ulong pageEnd = pageStart + (ulong)pages;
|
||||
|
||||
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||
|
||||
ulong mask = startMask;
|
||||
|
||||
while (pageIndex <= pageEndIndex)
|
||||
{
|
||||
if (pageIndex == pageEndIndex)
|
||||
{
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
|
||||
ulong pte;
|
||||
ulong mappedMask;
|
||||
|
||||
// Map all 2-bit entries that are unmapped.
|
||||
do
|
||||
{
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
|
||||
mappedMask = pte | (pte >> 1);
|
||||
mappedMask |= (mappedMask & BlockMappedMask) << 1;
|
||||
mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged.
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte);
|
||||
|
||||
mask = ulong.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the given address mapping from the page table.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual memory address</param>
|
||||
/// <param name="size">Size to be unmapped</param>
|
||||
private void RemoveMapping(ulong va, ulong size)
|
||||
{
|
||||
int pages = GetPagesCount(va, size, out _);
|
||||
ulong pageStart = va >> PageBits;
|
||||
ulong pageEnd = pageStart + (ulong)pages;
|
||||
|
||||
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||
|
||||
startMask = ~startMask;
|
||||
endMask = ~endMask;
|
||||
|
||||
ulong mask = startMask;
|
||||
|
||||
while (pageIndex <= pageEndIndex)
|
||||
{
|
||||
if (pageIndex == pageEndIndex)
|
||||
{
|
||||
mask |= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
ulong pte;
|
||||
|
||||
do
|
||||
{
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte);
|
||||
|
||||
mask = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of resources used by the memory manager.
|
||||
/// </summary>
|
||||
|
@ -776,10 +447,16 @@ namespace Ryujinx.Cpu.Jit
|
|||
_memoryEh.Dispose();
|
||||
}
|
||||
|
||||
protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size)
|
||||
protected override Memory<byte> GetPhysicalAddressMemory(nuint pa, int size)
|
||||
=> _addressSpace.Mirror.GetMemory(pa, size);
|
||||
|
||||
protected override Span<byte> GetPhysicalAddressSpan(nuint pa, int size)
|
||||
=> _addressSpace.Mirror.GetSpan(pa, size);
|
||||
|
||||
protected override ulong TranslateVirtualAddressForRead(ulong va)
|
||||
=> va;
|
||||
protected override nuint TranslateVirtualAddressChecked(ulong va)
|
||||
=> (nuint)GetPhysicalAddressChecked(va);
|
||||
|
||||
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
|
||||
=> (nuint)GetPhysicalAddressInternal(va);
|
||||
}
|
||||
}
|
||||
|
|
634
src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs
Normal file
634
src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs
Normal file
|
@ -0,0 +1,634 @@
|
|||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Cpu.Jit.HostTracked;
|
||||
using Ryujinx.Cpu.Signal;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
|
||||
/// </summary>
|
||||
public sealed class MemoryManagerHostTracked : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked
|
||||
{
|
||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||
private readonly bool _unsafeMode;
|
||||
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
|
||||
public int AddressSpaceBits { get; }
|
||||
|
||||
public MemoryTracking Tracking { get; }
|
||||
|
||||
private readonly NativePageTable _nativePageTable;
|
||||
private readonly AddressSpacePartitioned _addressSpace;
|
||||
|
||||
private readonly ManagedPageFlags _pages;
|
||||
|
||||
protected override ulong AddressSpaceSize { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool UsesPrivateAllocations => true;
|
||||
|
||||
public IntPtr PageTablePointer => _nativePageTable.PageTablePointer;
|
||||
|
||||
public MemoryManagerType Type => _unsafeMode ? MemoryManagerType.HostTrackedUnsafe : MemoryManagerType.HostTracked;
|
||||
|
||||
public event Action<ulong, ulong> UnmapEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the host tracked memory manager.
|
||||
/// </summary>
|
||||
/// <param name="backingMemory">Physical backing memory where virtual memory will be mapped to</param>
|
||||
/// <param name="addressSpaceSize">Size of the address space</param>
|
||||
/// <param name="unsafeMode">True if unmanaged access should not be masked (unsafe), false otherwise.</param>
|
||||
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
|
||||
public MemoryManagerHostTracked(MemoryBlock backingMemory, ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler)
|
||||
{
|
||||
bool useProtectionMirrors = MemoryBlock.GetPageSize() > PageSize;
|
||||
|
||||
Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler, useProtectionMirrors);
|
||||
|
||||
_backingMemory = backingMemory;
|
||||
_invalidAccessHandler = invalidAccessHandler;
|
||||
_unsafeMode = unsafeMode;
|
||||
AddressSpaceSize = addressSpaceSize;
|
||||
|
||||
ulong asSize = PageSize;
|
||||
int asBits = PageBits;
|
||||
|
||||
while (asSize < AddressSpaceSize)
|
||||
{
|
||||
asSize <<= 1;
|
||||
asBits++;
|
||||
}
|
||||
|
||||
AddressSpaceBits = asBits;
|
||||
|
||||
if (useProtectionMirrors && !NativeSignalHandler.SupportsFaultAddressPatching())
|
||||
{
|
||||
// Currently we require being able to change the fault address to something else
|
||||
// in order to "emulate" 4KB granularity protection on systems with larger page size.
|
||||
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
_pages = new ManagedPageFlags(asBits);
|
||||
_nativePageTable = new(asSize);
|
||||
_addressSpace = new(Tracking, backingMemory, _nativePageTable, useProtectionMirrors);
|
||||
}
|
||||
|
||||
public override ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return ReadOnlySequence<byte>.Empty;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (tracked)
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)size, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertValidAddressAndSize(va, (ulong)size);
|
||||
}
|
||||
|
||||
ulong endVa = va + (ulong)size;
|
||||
int offset = 0;
|
||||
|
||||
BytesReadOnlySequenceSegment first = null, last = null;
|
||||
|
||||
while (va < endVa)
|
||||
{
|
||||
(MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(size - offset));
|
||||
|
||||
Memory<byte> physicalMemory = memory.GetMemory(rangeOffset, (int)copySize);
|
||||
|
||||
if (first is null)
|
||||
{
|
||||
first = last = new BytesReadOnlySequenceSegment(physicalMemory);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (last.IsContiguousWith(physicalMemory, out nuint contiguousStart, out int contiguousSize))
|
||||
{
|
||||
Memory<byte> contiguousPhysicalMemory = new NativeMemoryManager<byte>(contiguousStart, contiguousSize).Memory;
|
||||
|
||||
last.Replace(contiguousPhysicalMemory);
|
||||
}
|
||||
else
|
||||
{
|
||||
last = last.Append(physicalMemory);
|
||||
}
|
||||
}
|
||||
|
||||
va += copySize;
|
||||
offset += (int)copySize;
|
||||
}
|
||||
|
||||
return new ReadOnlySequence<byte>(first, 0, last, (int)(size - last.RunningIndex));
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
return ReadOnlySequence<byte>.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
if (flags.HasFlag(MemoryMapFlags.Private))
|
||||
{
|
||||
_addressSpace.Map(va, pa, size);
|
||||
}
|
||||
|
||||
_pages.AddMapping(va, size);
|
||||
_nativePageTable.Map(va, pa, size, _addressSpace, _backingMemory, flags.HasFlag(MemoryMapFlags.Private));
|
||||
|
||||
Tracking.Map(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
_addressSpace.Unmap(va, size);
|
||||
|
||||
UnmapEvent?.Invoke(va, size);
|
||||
Tracking.Unmap(va, size);
|
||||
|
||||
_pages.RemoveMapping(va, size);
|
||||
_nativePageTable.Unmap(va, size);
|
||||
}
|
||||
|
||||
public override T ReadTracked<T>(ulong va)
|
||||
{
|
||||
try
|
||||
{
|
||||
return base.ReadTracked<T>(va);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Read(ulong va, Span<byte> data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
AssertValidAddressAndSize(va, (ulong)data.Length);
|
||||
|
||||
ulong endVa = va + (ulong)data.Length;
|
||||
int offset = 0;
|
||||
|
||||
while (va < endVa)
|
||||
{
|
||||
(MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset));
|
||||
|
||||
memory.GetSpan(rangeOffset, (int)copySize).CopyTo(data.Slice(offset, (int)copySize));
|
||||
|
||||
va += copySize;
|
||||
offset += (int)copySize;
|
||||
}
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SignalMemoryTracking(va, (ulong)data.Length, false);
|
||||
|
||||
if (TryGetVirtualContiguous(va, data.Length, out MemoryBlock memoryBlock, out ulong offset))
|
||||
{
|
||||
var target = memoryBlock.GetSpan(offset, data.Length);
|
||||
|
||||
bool changed = !data.SequenceEqual(target);
|
||||
|
||||
if (changed)
|
||||
{
|
||||
data.CopyTo(target);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteImpl(va, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return ReadOnlySpan<byte>.Empty;
|
||||
}
|
||||
|
||||
if (tracked)
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)size, false);
|
||||
}
|
||||
|
||||
if (TryGetVirtualContiguous(va, size, out MemoryBlock memoryBlock, out ulong offset))
|
||||
{
|
||||
return memoryBlock.GetSpan(offset, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> data = new byte[size];
|
||||
|
||||
Read(va, data);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public override WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return new WritableRegion(null, va, Memory<byte>.Empty);
|
||||
}
|
||||
|
||||
if (tracked)
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)size, true);
|
||||
}
|
||||
|
||||
if (TryGetVirtualContiguous(va, size, out MemoryBlock memoryBlock, out ulong offset))
|
||||
{
|
||||
return new WritableRegion(null, va, memoryBlock.GetMemory(offset, size));
|
||||
}
|
||||
else
|
||||
{
|
||||
IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size);
|
||||
|
||||
Read(va, memoryOwner.Memory.Span);
|
||||
|
||||
return new WritableRegion(this, va, memoryOwner);
|
||||
}
|
||||
}
|
||||
|
||||
public ref T GetRef<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
if (!TryGetVirtualContiguous(va, Unsafe.SizeOf<T>(), out MemoryBlock memory, out ulong offset))
|
||||
{
|
||||
ThrowMemoryNotContiguous();
|
||||
}
|
||||
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
|
||||
|
||||
return ref memory.GetRef<T>(offset);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool IsMapped(ulong va)
|
||||
{
|
||||
return ValidateAddress(va) && _pages.IsMapped(va);
|
||||
}
|
||||
|
||||
public bool IsRangeMapped(ulong va, ulong size)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
return _pages.IsRangeMapped(va, size);
|
||||
}
|
||||
|
||||
private bool TryGetVirtualContiguous(ulong va, int size, out MemoryBlock memory, out ulong offset)
|
||||
{
|
||||
if (_addressSpace.HasAnyPrivateAllocation(va, (ulong)size, out PrivateRange range))
|
||||
{
|
||||
// If we have a private allocation overlapping the range,
|
||||
// then the access is only considered contiguous if it covers the entire range.
|
||||
|
||||
if (range.Memory != null)
|
||||
{
|
||||
memory = range.Memory;
|
||||
offset = range.Offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
memory = null;
|
||||
offset = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
memory = _backingMemory;
|
||||
offset = GetPhysicalAddressInternal(va);
|
||||
|
||||
return IsPhysicalContiguous(va, size);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsPhysicalContiguous(ulong va, int size)
|
||||
{
|
||||
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int pages = GetPagesCount(va, (uint)size, out va);
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
if (!ValidateAddress(va + PageSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
va += PageSize;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ulong GetContiguousSize(ulong va, ulong size)
|
||||
{
|
||||
ulong contiguousSize = PageSize - (va & PageMask);
|
||||
|
||||
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
|
||||
{
|
||||
return contiguousSize;
|
||||
}
|
||||
|
||||
int pages = GetPagesCount(va, size, out va);
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
if (!ValidateAddress(va + PageSize))
|
||||
{
|
||||
return contiguousSize;
|
||||
}
|
||||
|
||||
if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize))
|
||||
{
|
||||
return contiguousSize;
|
||||
}
|
||||
|
||||
va += PageSize;
|
||||
contiguousSize += PageSize;
|
||||
}
|
||||
|
||||
return Math.Min(contiguousSize, size);
|
||||
}
|
||||
|
||||
private (MemoryBlock, ulong, ulong) GetMemoryOffsetAndSize(ulong va, ulong size)
|
||||
{
|
||||
PrivateRange privateRange = _addressSpace.GetFirstPrivateAllocation(va, size, out ulong nextVa);
|
||||
|
||||
if (privateRange.Memory != null)
|
||||
{
|
||||
return (privateRange.Memory, privateRange.Offset, privateRange.Size);
|
||||
}
|
||||
|
||||
ulong physSize = GetContiguousSize(va, Math.Min(size, nextVa - va));
|
||||
|
||||
return (_backingMemory, GetPhysicalAddressChecked(va), physSize);
|
||||
}
|
||||
|
||||
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
|
||||
{
|
||||
if (!ValidateAddressAndSize(va, size))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var regions = new List<HostMemoryRange>();
|
||||
ulong endVa = va + size;
|
||||
|
||||
try
|
||||
{
|
||||
while (va < endVa)
|
||||
{
|
||||
(MemoryBlock memory, ulong rangeOffset, ulong rangeSize) = GetMemoryOffsetAndSize(va, endVa - va);
|
||||
|
||||
regions.Add(new((UIntPtr)memory.GetPointer(rangeOffset, rangeSize), rangeSize));
|
||||
|
||||
va += rangeSize;
|
||||
}
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return Enumerable.Empty<MemoryRange>();
|
||||
}
|
||||
|
||||
return GetPhysicalRegionsImpl(va, size);
|
||||
}
|
||||
|
||||
private List<MemoryRange> GetPhysicalRegionsImpl(ulong va, ulong size)
|
||||
{
|
||||
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int pages = GetPagesCount(va, (uint)size, out va);
|
||||
|
||||
var regions = new List<MemoryRange>();
|
||||
|
||||
ulong regionStart = GetPhysicalAddressInternal(va);
|
||||
ulong regionSize = PageSize;
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
if (!ValidateAddress(va + PageSize))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ulong newPa = GetPhysicalAddressInternal(va + PageSize);
|
||||
|
||||
if (GetPhysicalAddressInternal(va) + PageSize != newPa)
|
||||
{
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
regionStart = newPa;
|
||||
regionSize = 0;
|
||||
}
|
||||
|
||||
va += PageSize;
|
||||
regionSize += PageSize;
|
||||
}
|
||||
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
|
||||
/// </remarks>
|
||||
public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
if (precise)
|
||||
{
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Software table, used for managed memory tracking.
|
||||
|
||||
_pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
|
||||
}
|
||||
|
||||
public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None)
|
||||
{
|
||||
return Tracking.BeginTracking(address, size, id, flags);
|
||||
}
|
||||
|
||||
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None)
|
||||
{
|
||||
return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags);
|
||||
}
|
||||
|
||||
public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
|
||||
{
|
||||
return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
|
||||
}
|
||||
|
||||
private ulong GetPhysicalAddressChecked(ulong va)
|
||||
{
|
||||
if (!IsMapped(va))
|
||||
{
|
||||
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}");
|
||||
}
|
||||
|
||||
return GetPhysicalAddressInternal(va);
|
||||
}
|
||||
|
||||
private ulong GetPhysicalAddressInternal(ulong va)
|
||||
{
|
||||
return _nativePageTable.GetPhysicalAddress(va);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest)
|
||||
{
|
||||
if (guest)
|
||||
{
|
||||
_addressSpace.Reprotect(va, size, protection);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pages.TrackingReprotect(va, size, protection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of resources used by the memory manager.
|
||||
/// </summary>
|
||||
protected override void Destroy()
|
||||
{
|
||||
_addressSpace.Dispose();
|
||||
_nativePageTable.Dispose();
|
||||
}
|
||||
|
||||
protected override Memory<byte> GetPhysicalAddressMemory(nuint pa, int size)
|
||||
=> _backingMemory.GetMemory(pa, size);
|
||||
|
||||
protected override Span<byte> GetPhysicalAddressSpan(nuint pa, int size)
|
||||
=> _backingMemory.GetSpan(pa, size);
|
||||
|
||||
protected override void WriteImpl(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
try
|
||||
{
|
||||
AssertValidAddressAndSize(va, (ulong)data.Length);
|
||||
|
||||
ulong endVa = va + (ulong)data.Length;
|
||||
int offset = 0;
|
||||
|
||||
while (va < endVa)
|
||||
{
|
||||
(MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset));
|
||||
|
||||
data.Slice(offset, (int)copySize).CopyTo(memory.GetSpan(rangeOffset, (int)copySize));
|
||||
|
||||
va += copySize;
|
||||
offset += (int)copySize;
|
||||
}
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override nuint TranslateVirtualAddressChecked(ulong va)
|
||||
=> (nuint)GetPhysicalAddressChecked(va);
|
||||
|
||||
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
|
||||
=> (nuint)GetPhysicalAddressInternal(va);
|
||||
}
|
||||
}
|
|
@ -1126,11 +1126,23 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||
Operand destination64 = new(destination.Kind, OperandType.I64, destination.Value);
|
||||
Operand basePointer = new(regAlloc.FixedPageTableRegister, RegisterType.Integer, OperandType.I64);
|
||||
|
||||
if (mmType == MemoryManagerType.HostMapped || mmType == MemoryManagerType.HostMappedUnsafe)
|
||||
{
|
||||
// We don't need to mask the address for the safe mode, since it is already naturally limited to 32-bit
|
||||
// and can never reach out of the guest address space.
|
||||
// We don't need to mask the address for the safe mode, since it is already naturally limited to 32-bit
|
||||
// and can never reach out of the guest address space.
|
||||
|
||||
if (mmType.IsHostTracked())
|
||||
{
|
||||
int tempRegister = regAlloc.AllocateTempGprRegister();
|
||||
|
||||
Operand pte = new(tempRegister, RegisterType.Integer, OperandType.I64);
|
||||
|
||||
asm.Lsr(pte, guestAddress, new Operand(OperandKind.Constant, OperandType.I32, 12));
|
||||
asm.LdrRr(pte, basePointer, pte, ArmExtensionType.Uxtx, true);
|
||||
asm.Add(destination64, pte, guestAddress);
|
||||
|
||||
regAlloc.FreeTempGprRegister(tempRegister);
|
||||
}
|
||||
else if (mmType.IsHostMapped())
|
||||
{
|
||||
asm.Add(destination64, basePointer, guestAddress);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -1131,5 +1131,37 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsPartialRegisterUpdateMemory(this InstName name)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case InstName.Ld1AdvsimdSnglAsNoPostIndex:
|
||||
case InstName.Ld1AdvsimdSnglAsPostIndex:
|
||||
case InstName.Ld2AdvsimdSnglAsNoPostIndex:
|
||||
case InstName.Ld2AdvsimdSnglAsPostIndex:
|
||||
case InstName.Ld3AdvsimdSnglAsNoPostIndex:
|
||||
case InstName.Ld3AdvsimdSnglAsPostIndex:
|
||||
case InstName.Ld4AdvsimdSnglAsNoPostIndex:
|
||||
case InstName.Ld4AdvsimdSnglAsPostIndex:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsPrefetchMemory(this InstName name)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case InstName.PrfmImm:
|
||||
case InstName.PrfmLit:
|
||||
case InstName.PrfmReg:
|
||||
case InstName.Prfum:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm64
|
||||
{
|
||||
class RegisterAllocator
|
||||
{
|
||||
public const int MaxTemps = 1;
|
||||
public const int MaxTempsInclFixed = MaxTemps + 2;
|
||||
|
||||
private uint _gprMask;
|
||||
private readonly uint _fpSimdMask;
|
||||
private readonly uint _pStateMask;
|
||||
|
@ -25,7 +22,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
|
|||
public uint AllFpSimdMask => _fpSimdMask;
|
||||
public uint AllPStateMask => _pStateMask;
|
||||
|
||||
public RegisterAllocator(uint gprMask, uint fpSimdMask, uint pStateMask, bool hasHostCall)
|
||||
public RegisterAllocator(MemoryManagerType mmType, uint gprMask, uint fpSimdMask, uint pStateMask, bool hasHostCall)
|
||||
{
|
||||
_gprMask = gprMask;
|
||||
_fpSimdMask = fpSimdMask;
|
||||
|
@ -56,7 +53,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
|
|||
|
||||
BuildRegisterMap(_registerMap);
|
||||
|
||||
Span<int> tempRegisters = stackalloc int[MaxTemps];
|
||||
Span<int> tempRegisters = stackalloc int[CalculateMaxTemps(mmType)];
|
||||
|
||||
for (int index = 0; index < tempRegisters.Length; index++)
|
||||
{
|
||||
|
@ -150,5 +147,15 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
|
|||
{
|
||||
mask &= ~(1u << index);
|
||||
}
|
||||
|
||||
public static int CalculateMaxTemps(MemoryManagerType mmType)
|
||||
{
|
||||
return mmType.IsHostMapped() ? 1 : 2;
|
||||
}
|
||||
|
||||
public static int CalculateMaxTempsInclFixed(MemoryManagerType mmType)
|
||||
{
|
||||
return CalculateMaxTemps(mmType) + 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -247,7 +247,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
|
|||
}
|
||||
}
|
||||
|
||||
if (!flags.HasFlag(InstFlags.ReadRt))
|
||||
if (!flags.HasFlag(InstFlags.ReadRt) || name.IsPartialRegisterUpdateMemory())
|
||||
{
|
||||
if (flags.HasFlag(InstFlags.Rt))
|
||||
{
|
||||
|
@ -281,7 +281,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
|
|||
gprMask |= MaskFromIndex(ExtractRd(flags, encoding));
|
||||
}
|
||||
|
||||
if (!flags.HasFlag(InstFlags.ReadRt))
|
||||
if (!flags.HasFlag(InstFlags.ReadRt) || name.IsPartialRegisterUpdateMemory())
|
||||
{
|
||||
if (flags.HasFlag(InstFlags.Rt))
|
||||
{
|
||||
|
|
|
@ -316,7 +316,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
|||
uint pStateUseMask = multiBlock.GlobalUseMask.PStateMask;
|
||||
|
||||
CodeWriter writer = new();
|
||||
RegisterAllocator regAlloc = new(gprUseMask, fpSimdUseMask, pStateUseMask, multiBlock.HasHostCall);
|
||||
RegisterAllocator regAlloc = new(memoryManager.Type, gprUseMask, fpSimdUseMask, pStateUseMask, multiBlock.HasHostCall);
|
||||
RegisterSaveRestore rsr = new(
|
||||
regAlloc.AllGprMask & AbiConstants.GprCalleeSavedRegsMask,
|
||||
regAlloc.AllFpSimdMask & AbiConstants.FpSimdCalleeSavedRegsMask,
|
||||
|
|
|
@ -274,7 +274,8 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
|||
|
||||
uint tempGprUseMask = gprUseMask | instGprReadMask | instGprWriteMask;
|
||||
|
||||
if (CalculateAvailableTemps(tempGprUseMask) < CalculateRequiredGprTemps(tempGprUseMask) || totalInsts++ >= MaxInstructionsPerFunction)
|
||||
if (CalculateAvailableTemps(tempGprUseMask) < CalculateRequiredGprTemps(memoryManager.Type, tempGprUseMask) ||
|
||||
totalInsts++ >= MaxInstructionsPerFunction)
|
||||
{
|
||||
isTruncated = true;
|
||||
address -= 4UL;
|
||||
|
@ -378,9 +379,9 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
|||
return false;
|
||||
}
|
||||
|
||||
private static int CalculateRequiredGprTemps(uint gprUseMask)
|
||||
private static int CalculateRequiredGprTemps(MemoryManagerType mmType, uint gprUseMask)
|
||||
{
|
||||
return BitOperations.PopCount(gprUseMask & RegisterUtils.ReservedRegsMask) + RegisterAllocator.MaxTempsInclFixed;
|
||||
return BitOperations.PopCount(gprUseMask & RegisterUtils.ReservedRegsMask) + RegisterAllocator.CalculateMaxTempsInclFixed(mmType);
|
||||
}
|
||||
|
||||
private static int CalculateAvailableTemps(uint gprUseMask)
|
||||
|
|
|
@ -55,6 +55,16 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
|||
ulong pc,
|
||||
uint encoding)
|
||||
{
|
||||
if (name.IsPrefetchMemory() && mmType == MemoryManagerType.HostTrackedUnsafe)
|
||||
{
|
||||
// Prefetch to invalid addresses do not cause faults, so for memory manager
|
||||
// types where we need to access the page table before doing the prefetch,
|
||||
// we should make sure we won't try to access an out of bounds page table region.
|
||||
// To do this, we force the masked memory manager variant to be used.
|
||||
|
||||
mmType = MemoryManagerType.HostTracked;
|
||||
}
|
||||
|
||||
switch (addressForm)
|
||||
{
|
||||
case AddressForm.OffsetReg:
|
||||
|
@ -511,18 +521,48 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
|||
WriteAddressTranslation(asBits, mmType, regAlloc, ref asm, destination, guestAddress);
|
||||
}
|
||||
|
||||
private static void WriteAddressTranslation(int asBits, MemoryManagerType mmType, RegisterAllocator regAlloc, ref Assembler asm, Operand destination, ulong guestAddress)
|
||||
private static void WriteAddressTranslation(
|
||||
int asBits,
|
||||
MemoryManagerType mmType,
|
||||
RegisterAllocator regAlloc,
|
||||
ref Assembler asm,
|
||||
Operand destination,
|
||||
ulong guestAddress)
|
||||
{
|
||||
asm.Mov(destination, guestAddress);
|
||||
|
||||
WriteAddressTranslation(asBits, mmType, regAlloc, ref asm, destination, destination);
|
||||
}
|
||||
|
||||
private static void WriteAddressTranslation(int asBits, MemoryManagerType mmType, RegisterAllocator regAlloc, ref Assembler asm, Operand destination, Operand guestAddress)
|
||||
private static void WriteAddressTranslation(
|
||||
int asBits,
|
||||
MemoryManagerType mmType,
|
||||
RegisterAllocator regAlloc,
|
||||
ref Assembler asm,
|
||||
Operand destination,
|
||||
Operand guestAddress)
|
||||
{
|
||||
Operand basePointer = new(regAlloc.FixedPageTableRegister, RegisterType.Integer, OperandType.I64);
|
||||
|
||||
if (mmType == MemoryManagerType.HostMapped || mmType == MemoryManagerType.HostMappedUnsafe)
|
||||
if (mmType.IsHostTracked())
|
||||
{
|
||||
int tempRegister = regAlloc.AllocateTempGprRegister();
|
||||
|
||||
Operand pte = new(tempRegister, RegisterType.Integer, OperandType.I64);
|
||||
|
||||
asm.Lsr(pte, guestAddress, new Operand(OperandKind.Constant, OperandType.I32, 12));
|
||||
|
||||
if (mmType == MemoryManagerType.HostTracked)
|
||||
{
|
||||
asm.And(pte, pte, new Operand(OperandKind.Constant, OperandType.I64, ulong.MaxValue >> (64 - (asBits - 12))));
|
||||
}
|
||||
|
||||
asm.LdrRr(pte, basePointer, pte, ArmExtensionType.Uxtx, true);
|
||||
asm.Add(destination, pte, guestAddress);
|
||||
|
||||
regAlloc.FreeTempGprRegister(tempRegister);
|
||||
}
|
||||
else if (mmType.IsHostMapped())
|
||||
{
|
||||
if (mmType == MemoryManagerType.HostMapped)
|
||||
{
|
||||
|
|
|
@ -68,9 +68,9 @@ namespace Ryujinx.Cpu.LightningJit
|
|||
|
||||
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
||||
|
||||
if (memory.Type.IsHostMapped())
|
||||
if (memory.Type.IsHostMappedOrTracked())
|
||||
{
|
||||
NativeSignalHandler.InitializeSignalHandler(MemoryBlock.GetPageSize());
|
||||
NativeSignalHandler.InitializeSignalHandler();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
389
src/Ryujinx.Cpu/ManagedPageFlags.cs
Normal file
389
src/Ryujinx.Cpu/ManagedPageFlags.cs
Normal file
|
@ -0,0 +1,389 @@
|
|||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
/// <summary>
|
||||
/// A page bitmap that keeps track of mapped state and tracking protection
|
||||
/// for managed memory accesses (not using host page protection).
|
||||
/// </summary>
|
||||
internal readonly struct ManagedPageFlags
|
||||
{
|
||||
public const int PageBits = 12;
|
||||
public const int PageSize = 1 << PageBits;
|
||||
public const int PageMask = PageSize - 1;
|
||||
|
||||
private readonly ulong[] _pageBitmap;
|
||||
|
||||
public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry.
|
||||
public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set.
|
||||
|
||||
private enum ManagedPtBits : ulong
|
||||
{
|
||||
Unmapped = 0,
|
||||
Mapped,
|
||||
WriteTracked,
|
||||
ReadWriteTracked,
|
||||
|
||||
MappedReplicated = 0x5555555555555555,
|
||||
WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa,
|
||||
ReadWriteTrackedReplicated = ulong.MaxValue,
|
||||
}
|
||||
|
||||
public ManagedPageFlags(int addressSpaceBits)
|
||||
{
|
||||
int bits = Math.Max(0, addressSpaceBits - (PageBits + PageToPteShift));
|
||||
_pageBitmap = new ulong[1 << bits];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of pages in a virtual address range.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range</param>
|
||||
/// <param name="startVa">The virtual address of the beginning of the first page</param>
|
||||
/// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetPagesCount(ulong va, ulong size, out ulong startVa)
|
||||
{
|
||||
// WARNING: Always check if ulong does not overflow during the operations.
|
||||
startVa = va & ~(ulong)PageMask;
|
||||
ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
|
||||
|
||||
return (int)(vaSpan / PageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the page at a given CPU virtual address is mapped.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address to check</param>
|
||||
/// <returns>True if the address is mapped, false otherwise</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool IsMapped(ulong va)
|
||||
{
|
||||
ulong page = va >> PageBits;
|
||||
|
||||
int bit = (int)((page & 31) << 1);
|
||||
|
||||
int pageIndex = (int)(page >> PageToPteShift);
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
|
||||
return ((pte >> bit) & 3) != 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex)
|
||||
{
|
||||
startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1);
|
||||
endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1));
|
||||
|
||||
pageIndex = (int)(pageStart >> PageToPteShift);
|
||||
pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a memory range is mapped.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <returns>True if the entire range is mapped, false otherwise</returns>
|
||||
public readonly bool IsRangeMapped(ulong va, ulong size)
|
||||
{
|
||||
int pages = GetPagesCount(va, size, out _);
|
||||
|
||||
if (pages == 1)
|
||||
{
|
||||
return IsMapped(va);
|
||||
}
|
||||
|
||||
ulong pageStart = va >> PageBits;
|
||||
ulong pageEnd = pageStart + (ulong)pages;
|
||||
|
||||
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||
|
||||
// Check if either bit in each 2 bit page entry is set.
|
||||
// OR the block with itself shifted down by 1, and check the first bit of each entry.
|
||||
|
||||
ulong mask = BlockMappedMask & startMask;
|
||||
|
||||
while (pageIndex <= pageEndIndex)
|
||||
{
|
||||
if (pageIndex == pageEndIndex)
|
||||
{
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
|
||||
pte |= pte >> 1;
|
||||
if ((pte & mask) != mask)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
mask = BlockMappedMask;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reprotect a region of virtual memory for tracking.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address base</param>
|
||||
/// <param name="size">Size of the region to protect</param>
|
||||
/// <param name="protection">Memory protection to set</param>
|
||||
public readonly void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
||||
{
|
||||
// Protection is inverted on software pages, since the default value is 0.
|
||||
protection = (~protection) & MemoryPermission.ReadAndWrite;
|
||||
|
||||
int pages = GetPagesCount(va, size, out va);
|
||||
ulong pageStart = va >> PageBits;
|
||||
|
||||
if (pages == 1)
|
||||
{
|
||||
ulong protTag = protection switch
|
||||
{
|
||||
MemoryPermission.None => (ulong)ManagedPtBits.Mapped,
|
||||
MemoryPermission.Write => (ulong)ManagedPtBits.WriteTracked,
|
||||
_ => (ulong)ManagedPtBits.ReadWriteTracked,
|
||||
};
|
||||
|
||||
int bit = (int)((pageStart & 31) << 1);
|
||||
|
||||
ulong tagMask = 3UL << bit;
|
||||
ulong invTagMask = ~tagMask;
|
||||
|
||||
ulong tag = protTag << bit;
|
||||
|
||||
int pageIndex = (int)(pageStart >> PageToPteShift);
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||
|
||||
ulong pte;
|
||||
|
||||
do
|
||||
{
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
}
|
||||
while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong pageEnd = pageStart + (ulong)pages;
|
||||
|
||||
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||
|
||||
ulong mask = startMask;
|
||||
|
||||
ulong protTag = protection switch
|
||||
{
|
||||
MemoryPermission.None => (ulong)ManagedPtBits.MappedReplicated,
|
||||
MemoryPermission.Write => (ulong)ManagedPtBits.WriteTrackedReplicated,
|
||||
_ => (ulong)ManagedPtBits.ReadWriteTrackedReplicated,
|
||||
};
|
||||
|
||||
while (pageIndex <= pageEndIndex)
|
||||
{
|
||||
if (pageIndex == pageEndIndex)
|
||||
{
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
|
||||
ulong pte;
|
||||
ulong mappedMask;
|
||||
|
||||
// Change the protection of all 2 bit entries that are mapped.
|
||||
do
|
||||
{
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
|
||||
mappedMask = pte | (pte >> 1);
|
||||
mappedMask |= (mappedMask & BlockMappedMask) << 1;
|
||||
mappedMask &= mask; // Only update mapped pages within the given range.
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte);
|
||||
|
||||
mask = ulong.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alerts the memory tracking that a given region has been read from or written to.
|
||||
/// This should be called before read/write is performed.
|
||||
/// </summary>
|
||||
/// <param name="tracking">Memory tracking structure to call when pages are protected</param>
|
||||
/// <param name="va">Virtual address of the region</param>
|
||||
/// <param name="size">Size of the region</param>
|
||||
/// <param name="write">True if the region was written, false if read</param>
|
||||
/// <param name="exemptId">Optional ID of the handles that should not be signalled</param>
|
||||
/// <remarks>
|
||||
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void SignalMemoryTracking(MemoryTracking tracking, ulong va, ulong size, bool write, int? exemptId = null)
|
||||
{
|
||||
// Software table, used for managed memory tracking.
|
||||
|
||||
int pages = GetPagesCount(va, size, out _);
|
||||
ulong pageStart = va >> PageBits;
|
||||
|
||||
if (pages == 1)
|
||||
{
|
||||
ulong tag = (ulong)(write ? ManagedPtBits.WriteTracked : ManagedPtBits.ReadWriteTracked);
|
||||
|
||||
int bit = (int)((pageStart & 31) << 1);
|
||||
|
||||
int pageIndex = (int)(pageStart >> PageToPteShift);
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
ulong state = ((pte >> bit) & 3);
|
||||
|
||||
if (state >= tag)
|
||||
{
|
||||
tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
|
||||
return;
|
||||
}
|
||||
else if (state == 0)
|
||||
{
|
||||
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong pageEnd = pageStart + (ulong)pages;
|
||||
|
||||
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||
|
||||
ulong mask = startMask;
|
||||
|
||||
ulong anyTrackingTag = (ulong)ManagedPtBits.WriteTrackedReplicated;
|
||||
|
||||
while (pageIndex <= pageEndIndex)
|
||||
{
|
||||
if (pageIndex == pageEndIndex)
|
||||
{
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
ulong mappedMask = mask & BlockMappedMask;
|
||||
|
||||
ulong mappedPte = pte | (pte >> 1);
|
||||
if ((mappedPte & mappedMask) != mappedMask)
|
||||
{
|
||||
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
|
||||
}
|
||||
|
||||
pte &= mask;
|
||||
if ((pte & anyTrackingTag) != 0) // Search for any tracking.
|
||||
{
|
||||
// Writes trigger any tracking.
|
||||
// Only trigger tracking from reads if both bits are set on any page.
|
||||
if (write || (pte & (pte >> 1) & BlockMappedMask) != 0)
|
||||
{
|
||||
tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mask = ulong.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given address mapping to the page table.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual memory address</param>
|
||||
/// <param name="size">Size to be mapped</param>
|
||||
public readonly void AddMapping(ulong va, ulong size)
|
||||
{
|
||||
int pages = GetPagesCount(va, size, out _);
|
||||
ulong pageStart = va >> PageBits;
|
||||
ulong pageEnd = pageStart + (ulong)pages;
|
||||
|
||||
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||
|
||||
ulong mask = startMask;
|
||||
|
||||
while (pageIndex <= pageEndIndex)
|
||||
{
|
||||
if (pageIndex == pageEndIndex)
|
||||
{
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
|
||||
ulong pte;
|
||||
ulong mappedMask;
|
||||
|
||||
// Map all 2-bit entries that are unmapped.
|
||||
do
|
||||
{
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
|
||||
mappedMask = pte | (pte >> 1);
|
||||
mappedMask |= (mappedMask & BlockMappedMask) << 1;
|
||||
mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged.
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte);
|
||||
|
||||
mask = ulong.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the given address mapping from the page table.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual memory address</param>
|
||||
/// <param name="size">Size to be unmapped</param>
|
||||
public readonly void RemoveMapping(ulong va, ulong size)
|
||||
{
|
||||
int pages = GetPagesCount(va, size, out _);
|
||||
ulong pageStart = va >> PageBits;
|
||||
ulong pageEnd = pageStart + (ulong)pages;
|
||||
|
||||
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||
|
||||
startMask = ~startMask;
|
||||
endMask = ~endMask;
|
||||
|
||||
ulong mask = startMask;
|
||||
|
||||
while (pageIndex <= pageEndIndex)
|
||||
{
|
||||
if (pageIndex == pageEndIndex)
|
||||
{
|
||||
mask |= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
ulong pte;
|
||||
|
||||
do
|
||||
{
|
||||
pte = Volatile.Read(ref pageRef);
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte);
|
||||
|
||||
mask = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Cpu.Signal;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
|
@ -8,19 +9,27 @@ namespace Ryujinx.Cpu
|
|||
{
|
||||
public class MemoryEhMeilleure : IDisposable
|
||||
{
|
||||
private delegate bool TrackingEventDelegate(ulong address, ulong size, bool write);
|
||||
public delegate ulong TrackingEventDelegate(ulong address, ulong size, bool write);
|
||||
|
||||
private readonly MemoryTracking _tracking;
|
||||
private readonly TrackingEventDelegate _trackingEvent;
|
||||
|
||||
private readonly ulong _pageSize;
|
||||
|
||||
private readonly ulong _baseAddress;
|
||||
private readonly ulong _mirrorAddress;
|
||||
|
||||
public MemoryEhMeilleure(MemoryBlock addressSpace, MemoryBlock addressSpaceMirror, MemoryTracking tracking)
|
||||
public MemoryEhMeilleure(MemoryBlock addressSpace, MemoryBlock addressSpaceMirror, MemoryTracking tracking, TrackingEventDelegate trackingEvent = null)
|
||||
{
|
||||
_baseAddress = (ulong)addressSpace.Pointer;
|
||||
|
||||
ulong endAddress = _baseAddress + addressSpace.Size;
|
||||
|
||||
_trackingEvent = tracking.VirtualMemoryEvent;
|
||||
_tracking = tracking;
|
||||
_trackingEvent = trackingEvent ?? VirtualMemoryEvent;
|
||||
|
||||
_pageSize = MemoryBlock.GetPageSize();
|
||||
|
||||
bool added = NativeSignalHandler.AddTrackedRegion((nuint)_baseAddress, (nuint)endAddress, Marshal.GetFunctionPointerForDelegate(_trackingEvent));
|
||||
|
||||
if (!added)
|
||||
|
@ -28,7 +37,7 @@ namespace Ryujinx.Cpu
|
|||
throw new InvalidOperationException("Number of allowed tracked regions exceeded.");
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
if (OperatingSystem.IsWindows() && addressSpaceMirror != null)
|
||||
{
|
||||
// Add a tracking event with no signal handler for the mirror on Windows.
|
||||
// The native handler has its own code to check for the partial overlap race when regions are protected by accident,
|
||||
|
@ -46,6 +55,21 @@ namespace Ryujinx.Cpu
|
|||
}
|
||||
}
|
||||
|
||||
private ulong VirtualMemoryEvent(ulong address, ulong size, bool write)
|
||||
{
|
||||
ulong pageSize = _pageSize;
|
||||
ulong addressAligned = BitUtils.AlignDown(address, pageSize);
|
||||
ulong endAddressAligned = BitUtils.AlignUp(address + size, pageSize);
|
||||
ulong sizeAligned = endAddressAligned - addressAligned;
|
||||
|
||||
if (_tracking.VirtualMemoryEvent(addressAligned, sizeAligned, write))
|
||||
{
|
||||
return _baseAddress + address;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
|
|
@ -143,7 +143,7 @@ namespace Ryujinx.Cpu
|
|||
}
|
||||
}
|
||||
|
||||
public PrivateMemoryAllocator(int blockAlignment, MemoryAllocationFlags allocationFlags) : base(blockAlignment, allocationFlags)
|
||||
public PrivateMemoryAllocator(ulong blockAlignment, MemoryAllocationFlags allocationFlags) : base(blockAlignment, allocationFlags)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -180,10 +180,10 @@ namespace Ryujinx.Cpu
|
|||
|
||||
private readonly List<T> _blocks;
|
||||
|
||||
private readonly int _blockAlignment;
|
||||
private readonly ulong _blockAlignment;
|
||||
private readonly MemoryAllocationFlags _allocationFlags;
|
||||
|
||||
public PrivateMemoryAllocatorImpl(int blockAlignment, MemoryAllocationFlags allocationFlags)
|
||||
public PrivateMemoryAllocatorImpl(ulong blockAlignment, MemoryAllocationFlags allocationFlags)
|
||||
{
|
||||
_blocks = new List<T>();
|
||||
_blockAlignment = blockAlignment;
|
||||
|
@ -212,7 +212,7 @@ namespace Ryujinx.Cpu
|
|||
}
|
||||
}
|
||||
|
||||
ulong blockAlignedSize = BitUtils.AlignUp(size, (ulong)_blockAlignment);
|
||||
ulong blockAlignedSize = BitUtils.AlignUp(size, _blockAlignment);
|
||||
|
||||
var memory = new MemoryBlock(blockAlignedSize, _allocationFlags);
|
||||
var newBlock = createBlock(memory, blockAlignedSize);
|
||||
|
|
|
@ -70,7 +70,7 @@ namespace Ryujinx.Cpu.Signal
|
|||
config = new SignalHandlerConfig();
|
||||
}
|
||||
|
||||
public static void InitializeSignalHandler(ulong pageSize, Func<IntPtr, IntPtr, IntPtr> customSignalHandlerFactory = null)
|
||||
public static void InitializeSignalHandler(Func<IntPtr, IntPtr, IntPtr> customSignalHandlerFactory = null)
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
|
@ -90,7 +90,7 @@ namespace Ryujinx.Cpu.Signal
|
|||
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
_signalHandlerPtr = MapCode(NativeSignalHandlerGenerator.GenerateUnixSignalHandler(_handlerConfig, rangeStructSize, pageSize));
|
||||
_signalHandlerPtr = MapCode(NativeSignalHandlerGenerator.GenerateUnixSignalHandler(_handlerConfig, rangeStructSize));
|
||||
|
||||
if (customSignalHandlerFactory != null)
|
||||
{
|
||||
|
@ -107,7 +107,7 @@ namespace Ryujinx.Cpu.Signal
|
|||
config.StructAddressOffset = 40; // ExceptionInformation1
|
||||
config.StructWriteOffset = 32; // ExceptionInformation0
|
||||
|
||||
_signalHandlerPtr = MapCode(NativeSignalHandlerGenerator.GenerateWindowsSignalHandler(_handlerConfig, rangeStructSize, pageSize));
|
||||
_signalHandlerPtr = MapCode(NativeSignalHandlerGenerator.GenerateWindowsSignalHandler(_handlerConfig, rangeStructSize));
|
||||
|
||||
if (customSignalHandlerFactory != null)
|
||||
{
|
||||
|
@ -175,5 +175,10 @@ namespace Ryujinx.Cpu.Signal
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool SupportsFaultAddressPatching()
|
||||
{
|
||||
return NativeSignalHandlerGenerator.SupportsFaultAddressPatchingForHost();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
using Ryujinx.Memory;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
public abstract class VirtualMemoryManagerRefCountedBase<TVirtual, TPhysical> : VirtualMemoryManagerBase<TVirtual, TPhysical>, IRefCounted
|
||||
where TVirtual : IBinaryInteger<TVirtual>
|
||||
where TPhysical : IBinaryInteger<TPhysical>
|
||||
public abstract class VirtualMemoryManagerRefCountedBase : VirtualMemoryManagerBase, IRefCounted
|
||||
{
|
||||
private int _referenceCount;
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -143,11 +145,11 @@ namespace Ryujinx.Graphics.Device
|
|||
}
|
||||
else
|
||||
{
|
||||
Memory<byte> memory = new byte[size];
|
||||
IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size);
|
||||
|
||||
GetSpan(va, size).CopyTo(memory.Span);
|
||||
GetSpan(va, size).CopyTo(memoryOwner.Memory.Span);
|
||||
|
||||
return new WritableRegion(this, va, memory, tracked: true);
|
||||
return new WritableRegion(this, va, memoryOwner, tracked: true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ namespace Ryujinx.Graphics.GAL
|
|||
public readonly bool SupportsMismatchingViewFormat;
|
||||
public readonly bool SupportsCubemapView;
|
||||
public readonly bool SupportsNonConstantTextureOffset;
|
||||
public readonly bool SupportsQuads;
|
||||
public readonly bool SupportsSeparateSampler;
|
||||
public readonly bool SupportsShaderBallot;
|
||||
public readonly bool SupportsShaderBarrierDivergence;
|
||||
public readonly bool SupportsShaderFloat64;
|
||||
|
@ -92,6 +94,8 @@ namespace Ryujinx.Graphics.GAL
|
|||
bool supportsMismatchingViewFormat,
|
||||
bool supportsCubemapView,
|
||||
bool supportsNonConstantTextureOffset,
|
||||
bool supportsQuads,
|
||||
bool supportsSeparateSampler,
|
||||
bool supportsShaderBallot,
|
||||
bool supportsShaderBarrierDivergence,
|
||||
bool supportsShaderFloat64,
|
||||
|
@ -144,6 +148,8 @@ namespace Ryujinx.Graphics.GAL
|
|||
SupportsMismatchingViewFormat = supportsMismatchingViewFormat;
|
||||
SupportsCubemapView = supportsCubemapView;
|
||||
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
|
||||
SupportsQuads = supportsQuads;
|
||||
SupportsSeparateSampler = supportsSeparateSampler;
|
||||
SupportsShaderBallot = supportsShaderBallot;
|
||||
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
|
||||
SupportsShaderFloat64 = supportsShaderFloat64;
|
||||
|
|
8
src/Ryujinx.Graphics.GAL/IImageArray.cs
Normal file
8
src/Ryujinx.Graphics.GAL/IImageArray.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public interface IImageArray
|
||||
{
|
||||
void SetFormats(int index, Format[] imageFormats);
|
||||
void SetImages(int index, ITexture[] images);
|
||||
}
|
||||
}
|
|
@ -59,6 +59,7 @@ namespace Ryujinx.Graphics.GAL
|
|||
void SetIndexBuffer(BufferRange buffer, IndexType type);
|
||||
|
||||
void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
|
||||
void SetImageArray(ShaderStage stage, int binding, IImageArray array);
|
||||
|
||||
void SetLineParameters(float width, bool smooth);
|
||||
|
||||
|
@ -89,6 +90,7 @@ namespace Ryujinx.Graphics.GAL
|
|||
void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers);
|
||||
|
||||
void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler);
|
||||
void SetTextureArray(ShaderStage stage, int binding, ITextureArray array);
|
||||
|
||||
void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
|
||||
void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers);
|
||||
|
|
|
@ -21,10 +21,14 @@ namespace Ryujinx.Graphics.GAL
|
|||
BufferHandle CreateBuffer(nint pointer, int size);
|
||||
BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers);
|
||||
|
||||
IImageArray CreateImageArray(int size, bool isBuffer);
|
||||
|
||||
IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
|
||||
|
||||
ISampler CreateSampler(SamplerCreateInfo info);
|
||||
ITexture CreateTexture(TextureCreateInfo info);
|
||||
ITextureArray CreateTextureArray(int size, bool isBuffer);
|
||||
|
||||
bool PrepareHostMapping(nint address, ulong size);
|
||||
|
||||
void CreateSync(ulong id, bool strict);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
|
@ -17,10 +17,34 @@ namespace Ryujinx.Graphics.GAL
|
|||
PinnedSpan<byte> GetData();
|
||||
PinnedSpan<byte> GetData(int layer, int level);
|
||||
|
||||
void SetData(SpanOrArray<byte> data);
|
||||
void SetData(SpanOrArray<byte> data, int layer, int level);
|
||||
void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region);
|
||||
/// <summary>
|
||||
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when
|
||||
/// the operation completes.
|
||||
/// </summary>
|
||||
/// <param name="data">Texture data bytes</param>
|
||||
void SetData(IMemoryOwner<byte> data);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when
|
||||
/// the operation completes.
|
||||
/// </summary>
|
||||
/// <param name="data">Texture data bytes</param>
|
||||
/// <param name="layer">Target layer</param>
|
||||
/// <param name="level">Target level</param>
|
||||
void SetData(IMemoryOwner<byte> data, int layer, int level);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when
|
||||
/// the operation completes.
|
||||
/// </summary>
|
||||
/// <param name="data">Texture data bytes</param>
|
||||
/// <param name="layer">Target layer</param>
|
||||
/// <param name="level">Target level</param>
|
||||
/// <param name="region">Target sub-region of the texture to update</param>
|
||||
void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region);
|
||||
|
||||
void SetStorage(BufferRange buffer);
|
||||
|
||||
void Release();
|
||||
}
|
||||
}
|
||||
|
|
8
src/Ryujinx.Graphics.GAL/ITextureArray.cs
Normal file
8
src/Ryujinx.Graphics.GAL/ITextureArray.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public interface ITextureArray
|
||||
{
|
||||
void SetSamplers(int index, ISampler[] samplers);
|
||||
void SetTextures(int index, ITexture[] textures);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Commands;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Program;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
@ -46,10 +48,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
Register<CreateBufferAccessCommand>(CommandType.CreateBufferAccess);
|
||||
Register<CreateBufferSparseCommand>(CommandType.CreateBufferSparse);
|
||||
Register<CreateHostBufferCommand>(CommandType.CreateHostBuffer);
|
||||
Register<CreateImageArrayCommand>(CommandType.CreateImageArray);
|
||||
Register<CreateProgramCommand>(CommandType.CreateProgram);
|
||||
Register<CreateSamplerCommand>(CommandType.CreateSampler);
|
||||
Register<CreateSyncCommand>(CommandType.CreateSync);
|
||||
Register<CreateTextureCommand>(CommandType.CreateTexture);
|
||||
Register<CreateTextureArrayCommand>(CommandType.CreateTextureArray);
|
||||
Register<GetCapabilitiesCommand>(CommandType.GetCapabilities);
|
||||
Register<PreFrameCommand>(CommandType.PreFrame);
|
||||
Register<ReportCounterCommand>(CommandType.ReportCounter);
|
||||
|
@ -63,6 +67,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose);
|
||||
Register<CounterEventFlushCommand>(CommandType.CounterEventFlush);
|
||||
|
||||
Register<ImageArraySetFormatsCommand>(CommandType.ImageArraySetFormats);
|
||||
Register<ImageArraySetImagesCommand>(CommandType.ImageArraySetImages);
|
||||
|
||||
Register<ProgramDisposeCommand>(CommandType.ProgramDispose);
|
||||
Register<ProgramGetBinaryCommand>(CommandType.ProgramGetBinary);
|
||||
Register<ProgramCheckLinkCommand>(CommandType.ProgramCheckLink);
|
||||
|
@ -82,6 +89,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion);
|
||||
Register<TextureSetStorageCommand>(CommandType.TextureSetStorage);
|
||||
|
||||
Register<TextureArraySetSamplersCommand>(CommandType.TextureArraySetSamplers);
|
||||
Register<TextureArraySetTexturesCommand>(CommandType.TextureArraySetTextures);
|
||||
|
||||
Register<WindowPresentCommand>(CommandType.WindowPresent);
|
||||
|
||||
Register<BarrierCommand>(CommandType.Barrier);
|
||||
|
@ -114,6 +124,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
Register<SetTransformFeedbackBuffersCommand>(CommandType.SetTransformFeedbackBuffers);
|
||||
Register<SetUniformBuffersCommand>(CommandType.SetUniformBuffers);
|
||||
Register<SetImageCommand>(CommandType.SetImage);
|
||||
Register<SetImageArrayCommand>(CommandType.SetImageArray);
|
||||
Register<SetIndexBufferCommand>(CommandType.SetIndexBuffer);
|
||||
Register<SetLineParametersCommand>(CommandType.SetLineParameters);
|
||||
Register<SetLogicOpStateCommand>(CommandType.SetLogicOpState);
|
||||
|
@ -130,6 +141,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
Register<SetScissorsCommand>(CommandType.SetScissor);
|
||||
Register<SetStencilTestCommand>(CommandType.SetStencilTest);
|
||||
Register<SetTextureAndSamplerCommand>(CommandType.SetTextureAndSampler);
|
||||
Register<SetTextureArrayCommand>(CommandType.SetTextureArray);
|
||||
Register<SetUserClipDistanceCommand>(CommandType.SetUserClipDistance);
|
||||
Register<SetVertexAttribsCommand>(CommandType.SetVertexAttribs);
|
||||
Register<SetVertexBuffersCommand>(CommandType.SetVertexBuffers);
|
||||
|
|
|
@ -7,10 +7,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
CreateBufferAccess,
|
||||
CreateBufferSparse,
|
||||
CreateHostBuffer,
|
||||
CreateImageArray,
|
||||
CreateProgram,
|
||||
CreateSampler,
|
||||
CreateSync,
|
||||
CreateTexture,
|
||||
CreateTextureArray,
|
||||
GetCapabilities,
|
||||
Unused,
|
||||
PreFrame,
|
||||
|
@ -25,6 +27,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
CounterEventDispose,
|
||||
CounterEventFlush,
|
||||
|
||||
ImageArraySetFormats,
|
||||
ImageArraySetImages,
|
||||
|
||||
ProgramDispose,
|
||||
ProgramGetBinary,
|
||||
ProgramCheckLink,
|
||||
|
@ -44,6 +49,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
TextureSetDataSliceRegion,
|
||||
TextureSetStorage,
|
||||
|
||||
TextureArraySetSamplers,
|
||||
TextureArraySetTextures,
|
||||
|
||||
WindowPresent,
|
||||
|
||||
Barrier,
|
||||
|
@ -76,6 +84,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
SetTransformFeedbackBuffers,
|
||||
SetUniformBuffers,
|
||||
SetImage,
|
||||
SetImageArray,
|
||||
SetIndexBuffer,
|
||||
SetLineParameters,
|
||||
SetLogicOpState,
|
||||
|
@ -92,6 +101,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
SetScissor,
|
||||
SetStencilTest,
|
||||
SetTextureAndSampler,
|
||||
SetTextureArray,
|
||||
SetUserClipDistance,
|
||||
SetVertexAttribs,
|
||||
SetVertexBuffers,
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray
|
||||
{
|
||||
struct ImageArraySetFormatsCommand : IGALCommand, IGALCommand<ImageArraySetFormatsCommand>
|
||||
{
|
||||
public readonly CommandType CommandType => CommandType.ImageArraySetFormats;
|
||||
private TableRef<ThreadedImageArray> _imageArray;
|
||||
private int _index;
|
||||
private TableRef<Format[]> _imageFormats;
|
||||
|
||||
public void Set(TableRef<ThreadedImageArray> imageArray, int index, TableRef<Format[]> imageFormats)
|
||||
{
|
||||
_imageArray = imageArray;
|
||||
_index = index;
|
||||
_imageFormats = imageFormats;
|
||||
}
|
||||
|
||||
public static void Run(ref ImageArraySetFormatsCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ThreadedImageArray imageArray = command._imageArray.Get(threaded);
|
||||
imageArray.Base.SetFormats(command._index, command._imageFormats.Get(threaded));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray
|
||||
{
|
||||
struct ImageArraySetImagesCommand : IGALCommand, IGALCommand<ImageArraySetImagesCommand>
|
||||
{
|
||||
public readonly CommandType CommandType => CommandType.ImageArraySetImages;
|
||||
private TableRef<ThreadedImageArray> _imageArray;
|
||||
private int _index;
|
||||
private TableRef<ITexture[]> _images;
|
||||
|
||||
public void Set(TableRef<ThreadedImageArray> imageArray, int index, TableRef<ITexture[]> images)
|
||||
{
|
||||
_imageArray = imageArray;
|
||||
_index = index;
|
||||
_images = images;
|
||||
}
|
||||
|
||||
public static void Run(ref ImageArraySetImagesCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ThreadedImageArray imageArray = command._imageArray.Get(threaded);
|
||||
imageArray.Base.SetImages(command._index, command._images.Get(threaded).Select(texture => ((ThreadedTexture)texture)?.Base).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||
{
|
||||
struct CreateImageArrayCommand : IGALCommand, IGALCommand<CreateImageArrayCommand>
|
||||
{
|
||||
public readonly CommandType CommandType => CommandType.CreateImageArray;
|
||||
private TableRef<ThreadedImageArray> _imageArray;
|
||||
private int _size;
|
||||
private bool _isBuffer;
|
||||
|
||||
public void Set(TableRef<ThreadedImageArray> imageArray, int size, bool isBuffer)
|
||||
{
|
||||
_imageArray = imageArray;
|
||||
_size = size;
|
||||
_isBuffer = isBuffer;
|
||||
}
|
||||
|
||||
public static void Run(ref CreateImageArrayCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
command._imageArray.Get(threaded).Base = renderer.CreateImageArray(command._size, command._isBuffer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||
{
|
||||
struct CreateTextureArrayCommand : IGALCommand, IGALCommand<CreateTextureArrayCommand>
|
||||
{
|
||||
public readonly CommandType CommandType => CommandType.CreateTextureArray;
|
||||
private TableRef<ThreadedTextureArray> _textureArray;
|
||||
private int _size;
|
||||
private bool _isBuffer;
|
||||
|
||||
public void Set(TableRef<ThreadedTextureArray> textureArray, int size, bool isBuffer)
|
||||
{
|
||||
_textureArray = textureArray;
|
||||
_size = size;
|
||||
_isBuffer = isBuffer;
|
||||
}
|
||||
|
||||
public static void Run(ref CreateTextureArrayCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
command._textureArray.Get(threaded).Base = renderer.CreateTextureArray(command._size, command._isBuffer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct SetImageArrayCommand : IGALCommand, IGALCommand<SetImageArrayCommand>
|
||||
{
|
||||
public readonly CommandType CommandType => CommandType.SetImageArray;
|
||||
private ShaderStage _stage;
|
||||
private int _binding;
|
||||
private TableRef<IImageArray> _array;
|
||||
|
||||
public void Set(ShaderStage stage, int binding, TableRef<IImageArray> array)
|
||||
{
|
||||
_stage = stage;
|
||||
_binding = binding;
|
||||
_array = array;
|
||||
}
|
||||
|
||||
public static void Run(ref SetImageArrayCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
renderer.Pipeline.SetImageArray(command._stage, command._binding, command._array.GetAs<ThreadedImageArray>(threaded)?.Base);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct SetTextureArrayCommand : IGALCommand, IGALCommand<SetTextureArrayCommand>
|
||||
{
|
||||
public readonly CommandType CommandType => CommandType.SetTextureArray;
|
||||
private ShaderStage _stage;
|
||||
private int _binding;
|
||||
private TableRef<ITextureArray> _array;
|
||||
|
||||
public void Set(ShaderStage stage, int binding, TableRef<ITextureArray> array)
|
||||
{
|
||||
_stage = stage;
|
||||
_binding = binding;
|
||||
_array = array;
|
||||
}
|
||||
|
||||
public static void Run(ref SetTextureArrayCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
renderer.Pipeline.SetTextureArray(command._stage, command._binding, command._array.GetAs<ThreadedTextureArray>(threaded)?.Base);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||
{
|
||||
|
@ -8,9 +8,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
|||
{
|
||||
public readonly CommandType CommandType => CommandType.TextureSetData;
|
||||
private TableRef<ThreadedTexture> _texture;
|
||||
private TableRef<byte[]> _data;
|
||||
private TableRef<IMemoryOwner<byte>> _data;
|
||||
|
||||
public void Set(TableRef<ThreadedTexture> texture, TableRef<byte[]> data)
|
||||
public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data)
|
||||
{
|
||||
_texture = texture;
|
||||
_data = data;
|
||||
|
@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
|||
public static void Run(ref TextureSetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ThreadedTexture texture = command._texture.Get(threaded);
|
||||
texture.Base.SetData(new ReadOnlySpan<byte>(command._data.Get(threaded)));
|
||||
texture.Base.SetData(command._data.Get(threaded));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||
{
|
||||
|
@ -8,11 +8,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
|||
{
|
||||
public readonly CommandType CommandType => CommandType.TextureSetDataSlice;
|
||||
private TableRef<ThreadedTexture> _texture;
|
||||
private TableRef<byte[]> _data;
|
||||
private TableRef<IMemoryOwner<byte>> _data;
|
||||
private int _layer;
|
||||
private int _level;
|
||||
|
||||
public void Set(TableRef<ThreadedTexture> texture, TableRef<byte[]> data, int layer, int level)
|
||||
public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data, int layer, int level)
|
||||
{
|
||||
_texture = texture;
|
||||
_data = data;
|
||||
|
@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
|||
public static void Run(ref TextureSetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ThreadedTexture texture = command._texture.Get(threaded);
|
||||
texture.Base.SetData(new ReadOnlySpan<byte>(command._data.Get(threaded)), command._layer, command._level);
|
||||
texture.Base.SetData(command._data.Get(threaded), command._layer, command._level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||
{
|
||||
|
@ -8,12 +8,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
|||
{
|
||||
public readonly CommandType CommandType => CommandType.TextureSetDataSliceRegion;
|
||||
private TableRef<ThreadedTexture> _texture;
|
||||
private TableRef<byte[]> _data;
|
||||
private TableRef<IMemoryOwner<byte>> _data;
|
||||
private int _layer;
|
||||
private int _level;
|
||||
private Rectangle<int> _region;
|
||||
|
||||
public void Set(TableRef<ThreadedTexture> texture, TableRef<byte[]> data, int layer, int level, Rectangle<int> region)
|
||||
public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data, int layer, int level, Rectangle<int> region)
|
||||
{
|
||||
_texture = texture;
|
||||
_data = data;
|
||||
|
@ -25,7 +25,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
|||
public static void Run(ref TextureSetDataSliceRegionCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ThreadedTexture texture = command._texture.Get(threaded);
|
||||
texture.Base.SetData(new ReadOnlySpan<byte>(command._data.Get(threaded)), command._layer, command._level, command._region);
|
||||
texture.Base.SetData(command._data.Get(threaded), command._layer, command._level, command._region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray
|
||||
{
|
||||
struct TextureArraySetSamplersCommand : IGALCommand, IGALCommand<TextureArraySetSamplersCommand>
|
||||
{
|
||||
public readonly CommandType CommandType => CommandType.TextureArraySetSamplers;
|
||||
private TableRef<ThreadedTextureArray> _textureArray;
|
||||
private int _index;
|
||||
private TableRef<ISampler[]> _samplers;
|
||||
|
||||
public void Set(TableRef<ThreadedTextureArray> textureArray, int index, TableRef<ISampler[]> samplers)
|
||||
{
|
||||
_textureArray = textureArray;
|
||||
_index = index;
|
||||
_samplers = samplers;
|
||||
}
|
||||
|
||||
public static void Run(ref TextureArraySetSamplersCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ThreadedTextureArray textureArray = command._textureArray.Get(threaded);
|
||||
textureArray.Base.SetSamplers(command._index, command._samplers.Get(threaded).Select(sampler => ((ThreadedSampler)sampler)?.Base).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray
|
||||
{
|
||||
struct TextureArraySetTexturesCommand : IGALCommand, IGALCommand<TextureArraySetTexturesCommand>
|
||||
{
|
||||
public readonly CommandType CommandType => CommandType.TextureArraySetTextures;
|
||||
private TableRef<ThreadedTextureArray> _textureArray;
|
||||
private int _index;
|
||||
private TableRef<ITexture[]> _textures;
|
||||
|
||||
public void Set(TableRef<ThreadedTextureArray> textureArray, int index, TableRef<ITexture[]> textures)
|
||||
{
|
||||
_textureArray = textureArray;
|
||||
_index = index;
|
||||
_textures = textures;
|
||||
}
|
||||
|
||||
public static void Run(ref TextureArraySetTexturesCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ThreadedTextureArray textureArray = command._textureArray.Get(threaded);
|
||||
textureArray.Base.SetTextures(command._index, command._textures.Get(threaded).Select(texture => ((ThreadedTexture)texture)?.Base).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||
{
|
||||
/// <summary>
|
||||
/// Threaded representation of a image array.
|
||||
/// </summary>
|
||||
class ThreadedImageArray : IImageArray
|
||||
{
|
||||
private readonly ThreadedRenderer _renderer;
|
||||
public IImageArray Base;
|
||||
|
||||
public ThreadedImageArray(ThreadedRenderer renderer)
|
||||
{
|
||||
_renderer = renderer;
|
||||
}
|
||||
|
||||
private TableRef<T> Ref<T>(T reference)
|
||||
{
|
||||
return new TableRef<T>(_renderer, reference);
|
||||
}
|
||||
|
||||
public void SetFormats(int index, Format[] imageFormats)
|
||||
{
|
||||
_renderer.New<ImageArraySetFormatsCommand>().Set(Ref(this), index, Ref(imageFormats));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetImages(int index, ITexture[] images)
|
||||
{
|
||||
_renderer.New<ImageArraySetImagesCommand>().Set(Ref(this), index, Ref(images));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||
{
|
||||
|
@ -110,21 +110,24 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
|||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetData(SpanOrArray<byte> data)
|
||||
/// <inheritdoc/>
|
||||
public void SetData(IMemoryOwner<byte> data)
|
||||
{
|
||||
_renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data.ToArray()));
|
||||
_renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetData(SpanOrArray<byte> data, int layer, int level)
|
||||
/// <inheritdoc/>
|
||||
public void SetData(IMemoryOwner<byte> data, int layer, int level)
|
||||
{
|
||||
_renderer.New<TextureSetDataSliceCommand>().Set(Ref(this), Ref(data.ToArray()), layer, level);
|
||||
_renderer.New<TextureSetDataSliceCommand>().Set(Ref(this), Ref(data), layer, level);
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region)
|
||||
/// <inheritdoc/>
|
||||
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
|
||||
{
|
||||
_renderer.New<TextureSetDataSliceRegionCommand>().Set(Ref(this), Ref(data.ToArray()), layer, level, region);
|
||||
_renderer.New<TextureSetDataSliceRegionCommand>().Set(Ref(this), Ref(data), layer, level, region);
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||
{
|
||||
/// <summary>
|
||||
/// Threaded representation of a texture and sampler array.
|
||||
/// </summary>
|
||||
class ThreadedTextureArray : ITextureArray
|
||||
{
|
||||
private readonly ThreadedRenderer _renderer;
|
||||
public ITextureArray Base;
|
||||
|
||||
public ThreadedTextureArray(ThreadedRenderer renderer)
|
||||
{
|
||||
_renderer = renderer;
|
||||
}
|
||||
|
||||
private TableRef<T> Ref<T>(T reference)
|
||||
{
|
||||
return new TableRef<T>(_renderer, reference);
|
||||
}
|
||||
|
||||
public void SetSamplers(int index, ISampler[] samplers)
|
||||
{
|
||||
_renderer.New<TextureArraySetSamplersCommand>().Set(Ref(this), index, Ref(samplers.ToArray()));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetTextures(int index, ITexture[] textures)
|
||||
{
|
||||
_renderer.New<TextureArraySetTexturesCommand>().Set(Ref(this), index, Ref(textures.ToArray()));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -183,6 +183,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetImageArray(ShaderStage stage, int binding, IImageArray array)
|
||||
{
|
||||
_renderer.New<SetImageArrayCommand>().Set(stage, binding, Ref(array));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetIndexBuffer(BufferRange buffer, IndexType type)
|
||||
{
|
||||
_renderer.New<SetIndexBufferCommand>().Set(buffer, type);
|
||||
|
@ -285,6 +291,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array)
|
||||
{
|
||||
_renderer.New<SetTextureArrayCommand>().Set(stage, binding, Ref(array));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
|
||||
{
|
||||
_renderer.New<SetTransformFeedbackBuffersCommand>().Set(_renderer.CopySpan(buffers));
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue