Cpu: add option to force all exclusive memory accesses to be ordered on AppleHv.

merge-requests/198/head
V380-Ori 2025-10-27 23:55:36 +02:00
parent 3394736b07
commit ac732a20b0
13 changed files with 172 additions and 4 deletions

View File

@ -267,6 +267,31 @@
"zh_TW": "使用 Hypervisor"
}
},
{
"ID": "SettingsTabSystemHvForceOrderedAtomics",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Force Ordered Atomics on Hypervisor",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "MenuBarFile",
"Translations": {
@ -16518,6 +16543,31 @@
"zh_TW": "使用 Hypervisor 取代 JIT。使用時可大幅提高效能但在目前狀態下可能不穩定。"
}
},
{
"ID": "HvForceOrderedAtomicsTooltip",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Force all exclusive memory accesses to be ordered on Hypervisor.\n\nSome games like The Legend of Zelda: Breath of the Wild and Splatoon 2 require this setting to be ON when using Hypervisor to prevent softlocks and crashes.\n\nLeave OFF if unsure.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "DRamTooltip",
"Translations": {

View File

@ -0,0 +1,62 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace Ryujinx.Cpu.AppleHv
{
static class HvCodePatcher
{
private const uint XMask = 0x3f808000u;
private const uint XValue = 0x8000000u;
private const uint ZrIndex = 31u;
public static void RewriteUnorderedExclusiveInstructions(Span<byte> code)
{
Span<uint> codeUint = MemoryMarshal.Cast<byte, uint>(code);
Span<Vector128<uint>> codeVector = MemoryMarshal.Cast<byte, Vector128<uint>>(code);
Vector128<uint> mask = Vector128.Create(XMask);
Vector128<uint> value = Vector128.Create(XValue);
for (int index = 0; index < codeVector.Length; index++)
{
Vector128<uint> v = codeVector[index];
if (Vector128.EqualsAny(Vector128.BitwiseAnd(v, mask), value))
{
int baseIndex = index * 4;
for (int instIndex = baseIndex; instIndex < baseIndex + 4; instIndex++)
{
ref uint inst = ref codeUint[instIndex];
if ((inst & XMask) != XValue)
{
continue;
}
bool isPair = (inst & (1u << 21)) != 0;
bool isLoad = (inst & (1u << 22)) != 0;
uint rt2 = (inst >> 10) & 0x1fu;
uint rs = (inst >> 16) & 0x1fu;
if (isLoad && rs != ZrIndex)
{
continue;
}
if (!isPair && rt2 != ZrIndex)
{
continue;
}
// Set the ordered flag.
inst |= 1u << 15;
}
}
}
}
}
}

View File

@ -25,6 +25,8 @@ namespace Ryujinx.Cpu.AppleHv
private readonly MemoryBlock _backingMemory;
private readonly PageTable<ulong> _pageTable;
private readonly bool _forceOrderedAtomics;
private readonly ManagedPageFlags _pages;
public bool UsesPrivateAllocations => false;
@ -46,11 +48,13 @@ namespace Ryujinx.Cpu.AppleHv
/// </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="forceOrderedAtomics">Force all exclusive memory accesses to be ordered</param>
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
public HvMemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null)
public HvMemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, bool forceOrderedAtomics, InvalidAccessHandler invalidAccessHandler = null)
{
_backingMemory = backingMemory;
_pageTable = new PageTable<ulong>();
_forceOrderedAtomics = forceOrderedAtomics;
_invalidAccessHandler = invalidAccessHandler;
AddressSpaceSize = addressSpaceSize;
@ -317,6 +321,18 @@ namespace Ryujinx.Cpu.AppleHv
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
{
if (_forceOrderedAtomics && protection.HasFlag(MemoryPermission.Execute))
{
// Some applications use unordered exclusive memory access instructions
// where it is not valid to do so, leading to memory re-ordering that
// makes the code behave incorrectly on some CPUs.
// To work around this, we force all such accesses to be ordered.
using WritableRegion writableRegion = GetWritableRegion(va, (int)size);
HvCodePatcher.RewriteUnorderedExclusiveInstructions(writableRegion.Memory.Span);
}
// TODO
}

View File

@ -55,7 +55,7 @@ namespace Ryujinx.HLE.HOS
if (OperatingSystem.IsMacOS() && isArm64Host && for64Bit && context.Device.Configuration.UseHypervisor)
{
HvEngine cpuEngine = new(_tickSource);
HvMemoryManager memoryManager = new(context.Memory, addressSpaceSize, invalidAccessHandler);
HvMemoryManager memoryManager = new(context.Memory, addressSpaceSize, context.Device.Configuration.HvForceOrderedAtomics, invalidAccessHandler);
processContext = new ArmProcessContext<HvMemoryManager>(pid, cpuEngine, _gpu, memoryManager, addressSpaceSize, for64Bit);
}
else

View File

@ -164,6 +164,11 @@ namespace Ryujinx.HLE
/// </summary>
internal readonly bool UseHypervisor;
/// <summary>
/// Force all exclusive memory accesses to be ordered on Hypervisor.
/// </summary>
internal readonly bool HvForceOrderedAtomics;
/// <summary>
/// Multiplayer LAN Interface ID (device GUID)
/// </summary>
@ -232,6 +237,7 @@ namespace Ryujinx.HLE
AspectRatio aspectRatio,
float audioVolume,
bool useHypervisor,
bool hvForceOrderedAtomics,
string multiplayerLanInterfaceId,
MultiplayerMode multiplayerMode,
bool multiplayerDisableP2p,
@ -261,6 +267,7 @@ namespace Ryujinx.HLE
AspectRatio = aspectRatio;
AudioVolume = audioVolume;
UseHypervisor = useHypervisor;
HvForceOrderedAtomics = hvForceOrderedAtomics;
MultiplayerLanInterfaceId = multiplayerLanInterfaceId;
MultiplayerMode = multiplayerMode;
MultiplayerDisableP2p = multiplayerDisableP2p;

View File

@ -333,6 +333,7 @@ namespace Ryujinx.Headless
options.AspectRatio,
options.AudioVolume,
options.UseHypervisor ?? true,
options.HvForceOrderedAtomics,
options.MultiplayerLanInterfaceId,
Common.Configuration.Multiplayer.MultiplayerMode.Disabled,
false,

View File

@ -79,6 +79,9 @@ namespace Ryujinx.Headless
if (NeedsOverride(nameof(UseHypervisor)) && OperatingSystem.IsMacOS())
UseHypervisor = configurationState.System.UseHypervisor;
if (NeedsOverride(nameof(HvForceOrderedAtomics)) && OperatingSystem.IsMacOS())
HvForceOrderedAtomics = configurationState.System.HvForceOrderedAtomics;
if (NeedsOverride(nameof(MultiplayerLanInterfaceId)))
MultiplayerLanInterfaceId = configurationState.Multiplayer.LanInterfaceId;
@ -339,6 +342,9 @@ namespace Ryujinx.Headless
[Option("use-hypervisor", Required = false, Default = true, HelpText = "Uses Hypervisor over JIT if available.")]
public bool? UseHypervisor { get; set; }
[Option("hv-force-ordered-atomics", Required = false, Default = false, HelpText = "Forces all exclusive memory accesses to be ordered on Hypervisor.")]
public bool HvForceOrderedAtomics { get; set; }
[Option("lan-interface-id", Required = false, Default = "0", HelpText = "GUID for the network interface used by LAN.")]
public string MultiplayerLanInterfaceId { get; set; }

View File

@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Systems.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 70;
public const int CurrentVersion = 71;
/// <summary>
/// Version of the configuration file format
@ -464,6 +464,11 @@ namespace Ryujinx.Ava.Systems.Configuration
/// </summary>
public bool UseHypervisor { get; set; }
/// <summary>
/// Force all exclusive memory accesses to be ordered on Hypervisor.
/// </summary>
public bool HvForceOrderedAtomics { get; set; }
/// <summary>
/// Enables or disables the GDB stub
/// </summary>

View File

@ -107,6 +107,7 @@ namespace Ryujinx.Ava.Systems.Configuration
System.IgnoreControllerApplet.Value = cff.IgnoreApplet;
System.SkipUserProfilesManager.Value = cff.SkipUserProfiles;
System.UseHypervisor.Value = cff.UseHypervisor;
System.HvForceOrderedAtomics.Value = cff.HvForceOrderedAtomics;
UI.GuiColumns.FavColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FavColumn : UI.GuiColumns.FavColumn.Value;
UI.GuiColumns.IconColumn.Value = shouldLoadFromFile ? cff.GuiColumns.IconColumn : UI.GuiColumns.IconColumn.Value;
@ -484,7 +485,8 @@ namespace Ryujinx.Ava.Systems.Configuration
};
}
),
(69, static cff => cff.SkipUserProfiles = false)
(69, static cff => cff.SkipUserProfiles = false),
(70, static cff => cff.HvForceOrderedAtomics = false)
);
}
}

View File

@ -412,6 +412,11 @@ namespace Ryujinx.Ava.Systems.Configuration
/// </summary>
public ReactiveObject<bool> UseHypervisor { get; private set; }
/// <summary>
/// Force all exclusive memory accesses to be ordered on Hypervisor.
/// </summary>
public ReactiveObject<bool> HvForceOrderedAtomics { get; private set; }
public SystemSection()
{
Language = new ReactiveObject<Language>();
@ -465,6 +470,8 @@ namespace Ryujinx.Ava.Systems.Configuration
AudioVolume.LogChangesToValue(nameof(AudioVolume));
UseHypervisor = new ReactiveObject<bool>();
UseHypervisor.LogChangesToValue(nameof(UseHypervisor));
HvForceOrderedAtomics = new ReactiveObject<bool>();
HvForceOrderedAtomics.LogChangesToValue(nameof(HvForceOrderedAtomics));
}
}
@ -936,6 +943,7 @@ namespace Ryujinx.Ava.Systems.Configuration
Graphics.AspectRatio,
System.AudioVolume,
System.UseHypervisor,
System.HvForceOrderedAtomics,
Multiplayer.LanInterfaceId,
Multiplayer.Mode,
Multiplayer.DisableP2p,

View File

@ -86,6 +86,7 @@ namespace Ryujinx.Ava.Systems.Configuration
IgnoreApplet = System.IgnoreControllerApplet,
SkipUserProfiles = System.SkipUserProfilesManager,
UseHypervisor = System.UseHypervisor,
HvForceOrderedAtomics = System.HvForceOrderedAtomics,
GuiColumns = new GuiColumns
{
FavColumn = UI.GuiColumns.FavColumn,
@ -215,6 +216,7 @@ namespace Ryujinx.Ava.Systems.Configuration
System.IgnoreControllerApplet.Value = false;
System.SkipUserProfilesManager.Value = false;
System.UseHypervisor.Value = true;
System.HvForceOrderedAtomics.Value = false;
Multiplayer.LanInterfaceId.Value = "0";
Multiplayer.Mode.Value = MultiplayerMode.Disabled;
Multiplayer.DisableP2p.Value = false;

View File

@ -276,6 +276,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsVulkanSelected =>
GraphicsBackendIndex == 1 || (GraphicsBackendIndex == 0 && !OperatingSystem.IsMacOS());
public bool UseHypervisor { get; set; }
public bool HvForceOrderedAtomics { get; set; }
public bool DisableP2P { get; set; }
public bool ShowDirtyHacks => ConfigurationState.Instance.Hacks.ShowDirtyHacks;
@ -673,6 +674,7 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableLowPowerPptc = config.System.EnableLowPowerPtc;
MemoryMode = (int)config.System.MemoryManagerMode.Value;
UseHypervisor = config.System.UseHypervisor;
HvForceOrderedAtomics = config.System.HvForceOrderedAtomics;
TurboMultiplier = config.System.TickScalar;
// Graphics
@ -784,6 +786,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.EnableLowPowerPtc.Value = EnableLowPowerPptc;
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
config.System.UseHypervisor.Value = UseHypervisor;
config.System.HvForceOrderedAtomics.Value = HvForceOrderedAtomics;
config.System.TickScalar.Value = TurboMultiplier;
// Graphics

View File

@ -71,6 +71,12 @@
<TextBlock Text="{ext:Locale SettingsTabSystemUseHypervisor}"
ToolTip.Tip="{ext:Locale UseHypervisorTooltip}" />
</CheckBox>
<CheckBox IsChecked="{Binding HvForceOrderedAtomics}"
IsVisible="{x:Static helper:RunningPlatform.IsArmMac}"
ToolTip.Tip="{ext:Locale HvForceOrderedAtomicsTooltip}">
<TextBlock Text="{ext:Locale SettingsTabSystemHvForceOrderedAtomics}"
ToolTip.Tip="{ext:Locale HvForceOrderedAtomicsTooltip}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<StackPanel