Author SHA1 Message Date
gdkchan db2c4a49e4 Start building more accurate vertex as compute usage info
- Fixes
2026-09-13 21:39:53 -05:00
gdkchan 95ab195207 Flip viewport based on screen scissor when Y negate is enabled 2026-09-13 21:39:53 -05:00
gdkchan 07d35ab0e3 Single block mode for LightningJit 2026-09-13 21:39:53 -05:00
gdkchan ab1afccb03 Do not set render targets as modified for discard-only draws
- Shader cache version bump
2026-09-13 21:39:53 -05:00
gdkchan 02b2ad6162 Prevent waits with zero timeout on Turnip 2026-09-13 21:39:53 -05:00
gdkchan a8b5501253 Implement PRMT shader instruction 2026-09-13 21:39:52 -05:00
gdkchan 4315f4795e Precise tracking of current PC address 2026-09-13 21:39:52 -05:00
gdkchan d18abffa65 Implement buffer texture alignment 2026-09-13 21:39:52 -05:00
gdkchan 551f29517d Add an alternative queue, used on macOS to work around SPIRV-Cross stack overflows
- Increase stack size to 2MB
2026-09-13 21:39:52 -05:00
GreemDev a0ad6f2246 misc: chore: add direct error code tuple to DisplayErrorAppletDialog
for use when i find the list of error codes -> causes
2026-09-13 21:24:52 -05:00
KeatonTheBot a412999eeb Fix Skip User Profiles
This and `Add the player select applet` fixes commit c9c78841b9.
2026-09-13 20:49:31 -05:00
JacobandGreemDev 87605ebce2 Add the player select applet
This introduces the somewhat completed version of the Player Select
Applet, allowing users to select either a user or a guest from the UI.
Note: Selecting the guest more then once currently does not work.

closes https://github.com/Ryubing/Ryujinx/issues/532

- misc: chore: optimize UserSelectorDialog closed handler

- misc: chore: Rename UserSelectorDialog to ProfileSelectorDialog

Co-authored-by: GreemDev <greemdev@ryujinx.app>
2026-09-13 20:48:20 -05:00
GreemDev b67e8df788 RenderDoc API support 2026-09-13 15:08:11 -05:00
Max 892fcd9444 Fixed rumble not being sent to the controller 2026-09-10 23:16:31 -05:00
KeatonTheBot 70cc617e31 Update NuGet packages
- Avalonia to 12.1.2

- Svg.Controls.Avalonia to 12.0.0.17

- Microsoft.IdentityModel.JsonWebTokens to 8.22.0

- Microsoft.NET.Test.Sdk to 18.10.0

- Ryujinx.Graphics.Vulkan.MoltenVK to 1.4.3-ryujinx.1

- System.IO.Hashing to 10.0.12
2026-09-09 21:45:41 -05:00
KeatonTheBot 9bf15801fd misc: Fix config file version 2026-09-08 16:13:26 -05:00
Max e3150d20bb [HID] Restructure HD Rumble class for future controller support
- Attempted fixing the strength: so far it hasn't been successful.
- Rumble should skip vibrations if they're not in-line with poll-rate: would like to come back to this. Queuing just does exactly what the hid buffer does, but our timer (poll rate) is not in sync with the rate the controller is reading at, which causes excess drops.
- Refactored the class so that implementing support for HD rumble for other controllers (DS5, Steam Controller) is much easier in the future.
2026-09-08 15:54:23 -05:00
Max 50538d601f [HID] Fixed HD Rumble latency 2026-09-08 15:54:23 -05:00
stossy11 5d3e392082 Fix fragment ClipDistance SPIR-V lookup
- Fixed ClipDistance being treated as VTG gl_PerVertex block, Causing the lookup of Input.Position when it's undeclared.
2026-09-08 15:54:23 -05:00
Neo c57e91cc59 Implement: TrySelectUserWithoutInteraction Command 52
Adds support for IAccountServiceForApplication command 52, TrySelectUserWithoutInteraction, introduced in Firmware 19.0.0.

Command 51 (TrySelectUserWithoutInteractionDeprecated) and command 52 use the existing ApplicationServiceServer.TrySelectUserWithoutInteraction implementation.

This prevents newer titles from failing when requesting automatic user selection through the new command.

Tomb Raider: Definitive Edition is now fully playable.
2026-09-08 15:54:22 -05:00
Neo 0381c7157b Fix: GetAllAvailableResources Filtering by Resource Path
Fix EmbeddedResources.GetAllAvailableResources to respect the specified resource path when enumerating embedded resources.

Previously, resources were filtered only by file extension. Adding locale-named JSON files under anywhere else caused them to be included when querying Assets/Locales, resulting in duplicate entries in the language menu.
2026-09-08 15:54:22 -05:00
avan 93b4c53c8a Fix crash on duplicate Build IDs in ModLoader.LoadCheats
Multiple executables may report the same Build ID, causing ToDictionary to throw an ArgumentException while creating the executable lookup.

Build the lookup incrementally and keep the first code address when duplicate Build IDs are encountered. Log a warning if a duplicate Build ID is associated with a different code address.
2026-09-08 15:54:22 -05:00
avan ef14467d1f Fix a hang during the loading stage
In KAddressArbiter, threads were originally inserted into the wait queue according to their DynamicPriority.

When multiple threads had the same DynamicPriority, the original implementation could not guarantee their existing order. Threads with the same dynamic priority are now inserted in FIFO order, preventing unstable wake-up ordering from blocking the guest synchronization flow.
2026-09-08 15:54:22 -05:00
KeatonTheBot e07f333a31 Raise application pool sizes for DRAM selections
- Fixes crash with BotW and possibly other games
2026-09-05 14:09:46 -05:00
KeatonTheBot fc4c45ef1d UI: Fix XCI file trimmer selection crash 2026-08-29 17:33:11 -05:00
Neo 52c6a1890e Improve "Add raw copy dependencies for incompatible textures"
Initial PR was made by Avan for Ryubing.

This allows for Trails in the Sky 1st Chapter (and by extension Trails through Daybreak, Trails through Daybreak II, Trails Beyond the Horizon, and the future Trails in the Sky 2nd Chapter – tested via demo – to be fully playable.

Initial PR caused issues on macOS devices, specifically crashing in certain games (such as Mario Kart 8 Deluxe and the aforementioned Trails games). It was reverted on the original Ryubing project (at the time of writing this description) for causing small rendering issues on The Legend of Zelda: Breath of the Wild.

The rendering issues do not appear on macOS.

This commit improves the original PR by eliminating said macOS crash.

Trails in the Sky 1st writes its exposure/brightness value through an R32G32Float 1x1 texture and later reads the raw bits back through an R32Uint 2x1 texture mapped to the same guest memory. The two texture formats/dimensions are fully incompatible as texture views, so they end
up as separate host textures, and nothing kept their contents coherent - the reader saw stale zeroed data, making the exposure calculation (and the rendered image) too dark. (This is the initial fix for the game)

TextureGroup already had a raw-copy dependency mechanism intended to
handle this class of alias (TextureDependency, TextureGroupHandle
raw-copy plumbing), gated by CanCreateRawCopyDependency's strict same-guest-memory checks. This commit fixes three bugs that kept that mechanism from working correctly (and thus crashing Trails and other games, such as Mario Kart 8 Deluxe):

(1) TextureGroup.InitializeOverlaps() and TextureGroup.RegisterIncompatibleOverlap() only forwarded overlaps to CreateCopyDependency() when compatibility was LayoutIncompatible or better, silently excluding the fully Incompatible case the raw-copy path exists for. Widened both guards to let Incompatible overlaps through so CanCreateRawCopyDependency actually gets a chance to run.

(2) Once Incompatible overlaps were allowed through, CreateCopyDependency(TextureGroup, ...) could still fall back to a regular, non-raw textureCopy for such pairs, since ViewLayoutCompatible/CopySizeMatches only check byte size and were never meant to reason about fully incompatible pairs (e.g. a depth format aliasing a color format with the same byte size). On Vulkan running through MoltenVK on macOS, that non-raw copy path requires a pixel-format-reinterpreting texture view, and MoltenVK/Metal refuses to create any view onto a depth-format texture ("not castable"), aborting emulation. textureCopy is now forced false for Incompatible pairs, leaving raw copy (which already excludes depth/stencil formats) as the only route for that severity level.

(3) TextureGroupHandle.Inherit() copied a handle's pending DeferredCopy to the new handle when a view was recreated, but did not copy DeferredCopyRaw alongside it. A handle that inherited a pending raw copy would then execute it through the regular (non-raw) CopyTo path once acknowledged, reinterpreting the source bytes with the wrong row layout and corrupting the image - visible as vertical flickering stripes whenever a view happened to be recreated with a raw copy still pending. DeferredCopyRaw is now carried over together with DeferredCopy.

(As mentioned earlier, vertical stripes aren't present on macOS, so (3) doesn't negatively have an effect on macOS in any way).
2026-08-29 10:00:58 -05:00
MaxandMythrax 2288084b53 HLE: Implemented ILockAccessor and ICommonStateGetter commands and stubs for Virtual Boy – Nintendo Classics #3
- Moves emulation past the initial frame by providing the necessary inputs the game expects. Apparently games like to spin until they get what they want.

Co-authored-by: Mythrax <mythrax@mythrax-rs.org>
2026-08-29 09:49:39 -05:00
Babib3l dd519a32b6 Hotfix for the PTC version, it's adjacent comment and PTC writer logging 2026-08-29 09:49:39 -05:00
Babib3l 7be8829b0a Wire "Start games in fullscreen" option to use the new fullscreen behaviour
Final (hopefully) fix for https://github.com/Ryubing/Issues/issues/415
2026-08-25 14:51:02 -05:00
Renovate Bot 6225e142fc Update Svg.Controls to 12.0.0.15 (#22)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Svg.Controls.Avalonia](https://github.com/wieslawsoltes/Svg.Skia) | `12.0.0.13` → `12.0.0.15` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Svg.Controls.Avalonia/12.0.0.15?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Svg.Controls.Avalonia/12.0.0.13/12.0.0.15?slim=true) |
| [Svg.Controls.Skia.Avalonia](https://github.com/wieslawsoltes/Svg.Skia) | `12.0.0.13` → `12.0.0.15` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Svg.Controls.Skia.Avalonia/12.0.0.15?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Svg.Controls.Skia.Avalonia/12.0.0.13/12.0.0.15?slim=true) |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/22
2026-08-25 04:17:37 +00:00
Renovate Bot 9e3c03a606 Update dependency FluentAvaloniaUI to 3.1.0 (#21)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [FluentAvaloniaUI](https://github.com/amwx/FluentAvalonia) | `3.0.2` → `3.1.0` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/FluentAvaloniaUI/3.1.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/FluentAvaloniaUI/3.0.2/3.1.0?slim=true) |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/21
2026-08-25 02:46:07 +00:00
KeatonTheBot 8e9bcb2a8e Update System.IO.Hashing to 10.0.11 2026-08-24 15:32:05 -05:00
Babib3l 7101f52c01 River 2 : HLE: Use per-program ownership for PTC disk caches
This PR threads process/program identity into PTC disk cache initialization so cache ownership is selected from the launched process rather than global/shared application state.

Previously, the PTC initialization path only propagated loose title/version information into the CPU layer. That was mostly fine for a single launched application, but will introduce many problems once multiple programs can be launched during the same session, given later processes could inherit cache identity from the first loaded application.
To address this issue, this PR introduces a `PtcCacheInfo` payload and applies it through the process context, CPU context, translator, and PTC initialization paths. Cache ownership is now resolved once the kernel process PID is known and includes:

- PID
- Program ID / Title ID
- Application ID
- Program index
- Display version
- Process kind
- Cache selector

`PtcCacheInfo` now owns its default title/application/version values, and `Ptc` uses that cache info directly instead of mirroring title/version fields internally.

The persistent cache key itself does remains title/version/selector based rather than PID based, so caches remain reusable across launches while still being selected from the correct process context. PID is only used for diagnostics and ownership tracing in logs.

Additional PTC logging has also been added to report cache ownership and selected paths during PTC initialization, Profiling info load/save and translation cache load/save

Both the PTC and profiler internal versions were bumped (a bunch of times lol).
2026-08-24 13:45:08 -05:00
avan e80fc00462 Fix view compatibility for compressed textures with different logical sizes
It has been confirmed that Divinity: Original Sin 2 may describe the same or overlapping guest GPU memory using BCn-compressed textures with different logical dimensions, such as 104×104 and 102×102.

BCn formats store texture data in blocks, with each BC block covering 4×4 texels. After rounding the dimensions up to complete blocks, both 104×104 and 102×102 textures require a 26×26 grid of BC blocks. As a result, they have the same block footprint at the base mip level. Ryujinx originally determined size compatibility in TextureCompatibility.ViewSizeMatches() primarily from this block footprint, which could cause these textures to be classified as Full view-compatible and allowed to share the same Vulkan VkImage directly.

However, because the two textures have different logical dimensions, their mip chains are also different. For example, at mip level 3, a 104×104 texture is reduced to 13×13, while a 102×102 texture is reduced to 12×12. If a 104×104 child texture view shares the backing image of a 102×102 parent texture, a subsequent full mip upload may attempt to write 13×13 compressed data into a Vulkan image subresource whose actual dimensions are only 12×12.

This produces a compressed buffer-to-image copy that does not match the geometry of the backing image. After the invalid command is submitted to the GPU, the Vulkan driver may asynchronously report VK_ERROR_DEVICE_LOST during command buffer submission, waiting, or presentation. In Ryujinx, this eventually appears as: VulkanException: Unexpected API error "ErrorDeviceLost".

The fix adds a logical-dimension check to TextureCompatibility.ViewSizeMatches(). If either the parent or child uses a compressed texture format, and the actual width or height of the corresponding parent mip does not match the logical width or height of the child, the relationship is no longer classified as Full view-compatible. Instead, it is downgraded to CopyOnly.

CopyOnly indicates that the two textures may still describe the same or overlapping guest memory and that their contents must remain synchronized, but they cannot directly share a single fixed-size Vulkan image. Ryujinx instead allows each texture to retain a host texture matching its own logical dimensions and synchronizes their contents through the existing copy-dependency mechanism.

As a result, the 13×13 mip of the 104×104 texture is uploaded to an actual 13×13 destination mip, while the 12×12 mip of the 102×102 texture is uploaded to an actual 12×12 destination mip.
2026-08-22 18:51:41 -05:00
avan 33cbd29c23 Fix bindless elimination failures observed in OCTOPATH TRAVELER 0:
Failed to find handle source for bindless access of type "textureBuffer".
2026-08-22 18:51:41 -05:00
avan cfc7c6039f Fix OpenGL program relaunch
The Enhanced and Classic versions of FFT are different program indices within the same application. When the Classic version is launched, the game uses ExecuteProgram to stop the current program, trigger DisposeGpu, create a new renderer and OpenGL context, and then launch the requested program.

Previously, when AppHost executed DisposeGpu, it attempted to bind the old OpenGL context and then called Device.DisposeGpu to destroy GPU resources. During the program relaunch flow, however, the old RendererHost may already have been removed from the visual tree, causing its native window to become detached.

If the old OpenGL context can no longer be bound, a ContextException is thrown, preventing the subsequent AppExit and program relaunch flow from continuing.

The updated implementation handles this case by detecting a ContextException while binding the OpenGL context when ShouldRestart is true. It then skips the destruction of GPU resources that depend on the old OpenGL context, allowing the old context to be released together with the old window. The AppExit and program relaunch flow can then continue without being interrupted.
2026-08-22 18:51:41 -05:00
avan ac6db0fe76 Fix Vulkan/OpenGL attachmentless rendering
Some fragment passes used by FFT do not have any color or depth/stencil attachments. Instead, the fragment shader writes the results directly to storage images (on OpenGL: through imageStore).

### Vulkan
When SetImage is called and FramebufferParams has no attachments, its virtual size must be updated using the width, height, and layer count of the fragment storage image. Otherwise, the attachmentless framebuffer may retain an incorrect 1x1 extent, causing backgrounds, logos, UI elements, and other rendered content to be missing.

If the first draw occurs before the storage-image descriptor is rebound, SetImage cannot yet provide the correct storage-image dimensions. Therefore, when RecreateGraphicsPipelineIfNeeded finds that FramebufferParams has no attachments and still uses the default 1x1 extent, it initializes the framebuffer dimensions from the active viewport. Otherwise, the first Vulkan draw may be restricted to a 1x1 area and render incorrectly.

The storage-image extent is therefore used as the authoritative size for FramebufferParams, while the active viewport is used as a fallback when the storage-image extent is not yet available before the first draw.

### OpenGL
A storage image is not a framebuffer attachment, so OpenGL cannot derive the framebuffer width and height from it. When the framebuffer has no attachments but a viewport with valid dimensions has already been defined, Pipeline.PreDraw must set the default framebuffer width and height from the active viewport.

Without non-zero default width and height values, the attachmentless framebuffer remains incomplete. Drawing with that framebuffer results in InvalidFramebufferOperation, so rasterization and fragment shader execution do not occur even when the storage images are bound correctly.
2026-08-22 18:51:41 -05:00
KeatonTheBot bed294bb92 UI: Fix Restart Emulation menu item not being clickable 2026-08-21 12:31:39 -05:00
Babib3l ed70f7921e feature: Add GpuDriverVersion to GPU info logging
- The Vulkan backend now reports the driver name and version using VK_KHR_driver_properties when available, with fallbacks for raw Vulkan driverVersion parsing. Intel Windows drivers also get the full package-style version from the driver UUID when exposed by the driver.
  OpenGL now carries its existing OpenGL version string through HardwareInfo.GpuDriverVersion.
2026-08-21 12:05:42 -05:00
LotPandLotP1 1a1fe53535 Fix a crash when no controller is connected
Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
2026-08-17 14:03:27 -05:00
KeatonTheBot 47e55acf21 UI: Update Avalonia to v12.1
- Avalonia to 12.1.1

- FluentAvalonia to 3.0.2

- Move DevTools Ctrl+F12 keybind to app initialization
2026-08-16 16:37:27 -05:00
avan 0e5fead0f9 Reject texture descriptors with unmapped addresses
Fix an "Invalid texture format 0x25A5A (sRGB: True)" error that occurs when running OCTOPATH TRAVELER 0.

The game may leave texture descriptor heap entries filled with 0x5A. Interpreting such uninitialized entries as valid texture descriptors produces an invalid format and causes texture creation to proceed with garbage descriptor data.

Validate the texture address before decoding the descriptor. Descriptors with a zero or unmapped address are marked as invalid and skipped. The invalid state is cleared when the corresponding texture pool entry is modified, allowing the descriptor to be evaluated again.
2026-08-10 00:05:44 -05:00
KeatonTheBotand_Neo_ d1fb89f746 UI: Fix sort timing
- Now sorts instantly when an option is selected

Co-authored-by: _Neo_ <ursamajorjanus2819@gmail.com>
2026-08-10 00:00:49 -05:00
KeatonTheBot cc1861f9bb UI: Fix Amiibo tag URL 2026-08-09 23:25:42 -05:00
avan 733bd0951b Fix shadow state handling for blend enable updates
Incorrect rendering was observed during loading transitions in Trails in the Sky 1st Chapter. Testing showed that routing the first render target's RT0 BlendEnable transition from disabled to enabled through the normal register write path prevented the issue.

Further investigation found that the normal register write was restoring Shadow RAM behavior that was missing from UpdateBlendEnable.

UpdateBlendEnable uses a fast bulk update path instead of issuing a normal register write for each render target. As a result, it bypasses the Shadow RAM handling provided by DeviceStateWithShadow.WriteWithRedundancyCheck.

A normal register write updates both State and ShadowState in MethodTrack and MethodTrackWithFilter modes. In MethodReplay mode, it ignores the incoming value and restores the value previously stored in ShadowState to State.

UpdateBlendEnable must reproduce the same behavior while retaining its bulk comparison and copy path. In track modes, copy the incoming enable values to shadowState. In replay mode, copy shadowState to the incoming enable span, then let the existing comparison and copy logic update state and mark BlendState dirty when necessary.

The original implementation incorrectly used state for both track and replay handling. This prevented Shadow RAM from correctly recording or replaying BlendEnable values, which could cause rendering state synchronization errors.

Correct the bulk update path to use ShadowState for tracking and replay. This preserves the optimized bulk operation while addressing the underlying issue without requiring an RT0-specific compatibility workaround.
2026-08-09 15:17:12 -05:00
mtmasantana 087e711e44 macOS: Fix NaN texture coordinates resolving toward max on Apple GPUs
- Fixes Animal Crossing: New Horizons backdrops being white instead of black

Patched applied from: https://github.com/Ryubing/Issues/issues/476
2026-08-05 14:04:43 -05:00
avan 9c46452388 Add raw copy dependencies for incompatible textures
Trails in the Sky 1st uses texture descriptors with different formats and dimensions that map to the same region of guest GPU memory. For example, the game interprets the same underlying data as both an R32Uint 2x1 texture and an R32G32Float 1x1 texture. Both textures have an 8-byte logical payload, but their texel formats and dimensions are different.

Ryubing cannot represent these two descriptors as normally compatible texture views, so it creates separate host texture objects for them. Since these host textures represent the same guest memory, a modification made to one of them should become visible to the other before the other texture is read. However, the existing synchronization mechanism does not fully handle this situation.

The game uses this pattern for its exposure or brightness history. A compute shader first writes the updated exposure data to the R32G32Float 1x1 host texture. A later operation then reads the raw bits of the same data through the R32Uint 2x1 host texture. The original Ryubing implementation did not synchronize the contents of these two separate host textures. As a result, the reading texture received stale zero values, which caused the shader to calculate an incorrectly low exposure value and made the rendered image appear too dark.

TextureGroup already has a guest-memory synchronization mechanism for incompatible overlaps. When _flushIncompatibleOverlaps is enabled, the most recent texture contents are written back to guest memory, after which the other texture reloads the data. However, this mechanism was primarily enabled through IsFormatHostIncompatible, which checks whether an individual texture format can be correctly supported by the host GPU.

In this case, both R32Uint and R32G32Float are individually supported by the host GPU. Therefore, IsFormatHostIncompatible does not enable this synchronization path. The issue is not that either format is unsupported. The issue is that two individually supported aliases, represented by separate host textures, do not maintain coherent contents.

In theory, the latest contents could be flushed to guest memory immediately after every GPU write to a host texture, allowing other textures that map to the same guest memory to reload the updated data. However, doing so could frequently trigger expensive operations such as GPU waits, data readbacks, guest texture layout conversions, and memory-tracking updates. This would be particularly costly for textures that are updated every frame or during every compute dispatch.

For this reason, the fix does not require an immediate flush after every write. Instead, it extends Ryubing's existing TextureDependency and TextureGroupHandle mechanisms. When two fully incompatible textures map to exactly the same guest memory and satisfy a strict set of raw-copy requirements, the program creates a raw-copy dependency between them.

When the source texture is modified, the target texture is only marked as requiring a raw copy. The latest logical raw bytes are copied from the source only when the target texture is actually about to be used. This keeps the contents of the separate host texture aliases coherent while avoiding unnecessary round trips through guest memory.
2026-08-04 23:17:36 -05:00
KeatonTheBot 0657b00622 SDL: Update game controller database on launch
- Downloads updated gamecontrollerdb.txt file on launch, only updates when a new file is available. This keeps the database updated between SDL releases.
2026-07-30 15:51:32 -05:00
KeatonTheBot 87cf0b0cbb Update System.IO.Hashing to 10.0.10 2026-07-27 21:55:14 -05:00
Renovate Bot 954332aa2a Update dependency Microsoft.NET.Test.Sdk to 18.8.1 (#20)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) | `18.7.0` → `18.8.1` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Microsoft.NET.Test.Sdk/18.8.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Microsoft.NET.Test.Sdk/18.7.0/18.8.1?slim=true) |

---

### Release Notes

<details>
<summary>microsoft/vstest (Microsoft.NET.Test.Sdk)</summary>

### [`v18.8.1`](https://github.com/microsoft/vstest/releases/tag/v18.8.1)

#### What's Changed

- Fix protocol negotiation timeout when STJ reflection is disabled (18.8.1) by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;16281](https://github.com/microsoft/vstest/pull/16281)

**Full Changelog**: <https://github.com/microsoft/vstest/compare/v18.8.0...v18.8.1>

### [`v18.8.0`](https://github.com/microsoft/vstest/releases/tag/v18.8.0)

#### What's Changed

- Migrate from Newtonsoft.Json to System.Text.Json / Jsonite (merge to main) by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15687](https://github.com/microsoft/vstest/pull/15687)
  - For more detail refer to <https://devblogs.microsoft.com/dotnet/vs-test-is-removing-its-newtonsoft-json-dependency/>
- Create source-only filter package by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15638](https://github.com/microsoft/vstest/pull/15638)
- Add ARM64 msdia140.dll support to test platform packages by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15692](https://github.com/microsoft/vstest/pull/15692)
- Fix mutex cleanup crash on macOS/Linux by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15684](https://github.com/microsoft/vstest/pull/15684)
- Restrict artifact temp directory permissions on Unix by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15729](https://github.com/microsoft/vstest/pull/15729)
- Add support for filtering uncategorized tests with TestCategory=None by [@&#8203;Evangelink](https://github.com/Evangelink) in [#&#8203;15727](https://github.com/microsoft/vstest/pull/15727)
- Fix SCI binding failure in DTA hosts (main) by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15724](https://github.com/microsoft/vstest/pull/15724)
- Fix HTML logger parallel file collision by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15435](https://github.com/microsoft/vstest/pull/15435)
- Improve error message when testhost cannot be found by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;16053](https://github.com/microsoft/vstest/pull/16053)
- Fix HTML logger exception on invalid XML chars in test display names by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;16051](https://github.com/microsoft/vstest/pull/16051)

**Full Changelog**: <https://github.com/microsoft/vstest/compare/v18.7.0...v18.8.0>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/20
2026-07-28 02:43:28 +00:00
Renovate Bot e650d3fb04 Update dependency Ryujinx.Graphics.Vulkan.MoltenVK to 1.4.2-ryujinx.6 (#18)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Ryujinx.Graphics.Vulkan.MoltenVK](https://github.com/Kenji-NX/MoltenVK) | `1.4.2-ryujinx.5` → `1.4.2-ryujinx.6` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Ryujinx.Graphics.Vulkan.MoltenVK/1.4.2-ryujinx.6?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Ryujinx.Graphics.Vulkan.MoltenVK/1.4.2-ryujinx.5/1.4.2-ryujinx.6?slim=true) |

---

### Release Notes

<details>
<summary>Kenji-NX/MoltenVK (Ryujinx.Graphics.Vulkan.MoltenVK)</summary>

### [`v1.4.2-ryujinx.6`](https://github.com/Kenji-NX/MoltenVK/releases/tag/v1.4.2-ryujinx.6)

##### Fork changes

- N/A

##### Upstream changes ([Changelog](https://github.com/KhronosGroup/MoltenVK/compare/67a91f3...db66022))

- Fix invalidation of cached Metal textures
- Add native sampler min/max filtering
- Move sampler reduction capability with format caps
- Move sampler reduction conversion to datatypes
- Raise minimum target to macOS 12.0 / iOS 15.0 / tvOS 15.0
- Do not enable subgroup emulation for reduction
- Fix descriptor pool alignment

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/18
2026-07-28 02:38:09 +00:00
Renovate Bot d578c5e2a9 Update dependency Microsoft.IdentityModel.JsonWebTokens to 8.21.0 (#19)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) | `8.19.1` → `8.21.0` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Microsoft.IdentityModel.JsonWebTokens/8.21.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Microsoft.IdentityModel.JsonWebTokens/8.19.1/8.21.0?slim=true) |

---

### Release Notes

<details>
<summary>AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet (Microsoft.IdentityModel.JsonWebTokens)</summary>

### [`v8.21.0`](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases/tag/8.21.0)

[Compare Source](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.20.0...8.21.0)

#### Performance Improvements

- Add JsonWebToken header-replacement path to avoid re-parsing payload. See [PR #&#8203;3553](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3553).
- Add claims dictionary preallocation. See [PR #&#8203;3550](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3550).

#### Bug Fixes

- Fix CI build failures: remove unused log message and add missing DPoP API entries. See [PR #&#8203;3551](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3551).

### [`v8.20.0`](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.19.2...8.20.0)

[Compare Source](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.19.2...8.20.0)

### [`v8.19.2`](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases/tag/8.19.2)

[Compare Source](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.19.1...8.19.2)

#### What's Changed

- Improve serializer and JWE key unwrap handling by [@&#8203;iNinja](https://github.com/iNinja) in [#&#8203;3544](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3544)

**Full Changelog**: <https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.19.1...8.19.2>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/19
2026-07-28 02:37:01 +00:00
LotPandLotP1 521a18f5a7 mono-jit-v2
second attempt at single layer address tables (first attempt https://git.ryujinx.app/projects/Ryubing/pulls/167).
should work with all games now.

Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
2026-07-17 16:47:59 -05:00
LotPandLotP1 4b255a98db Revert "mono-jit"
This reverts the mono jit PR.
It had several issues that I haven't had time to fix yet.
Expect mono jit v2 in the future with said fixes.

JitCache alignment fixes are still included/fixed.

Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
2026-07-14 15:43:51 -05:00
Renovate Bot 8b34270a22 Update dependency Ryujinx.Graphics.Vulkan.MoltenVK to 1.4.2-ryujinx.5 (#17)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Ryujinx.Graphics.Vulkan.MoltenVK](https://github.com/Kenji-NX/MoltenVK) | `1.4.2-ryujinx.4` → `1.4.2-ryujinx.5` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Ryujinx.Graphics.Vulkan.MoltenVK/1.4.2-ryujinx.5?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Ryujinx.Graphics.Vulkan.MoltenVK/1.4.2-ryujinx.4/1.4.2-ryujinx.5?slim=true) |

---

### Release Notes

<details>
<summary>Kenji-NX/MoltenVK (Ryujinx.Graphics.Vulkan.MoltenVK)</summary>

### [`v1.4.2-ryujinx.5`](https://github.com/Kenji-NX/MoltenVK/releases/tag/v1.4.2-ryujinx.5)

##### Fork changes

- CI changes
  - Build with macOS 26
  - Remove `all` build
  - Add iOS Private API build

##### Upstream changes ([Changelog](https://github.com/KhronosGroup/MoltenVK/compare/29a0606...4b715bd))

- Fix incorrect placement heap check in MVKImagePlane::getMTLTexture()
- Fix AMD subgroup size to 32 for correct Metal simdgroup operations
- Fix SPIRV-Cross: symlink instead of submodule
- Fix regressions and crashes on Mac 1 (NVIDIA/Intel) GPUs
  - Work around combined sampler arg buffers on NVIDIA Mac1
  - `nativeTextureSwizzle = false` for NVIDIA Mac1
  - Restore shader-side image view swizzle fallback.
  - Avoid input attachment argument buffers on NVIDIA Mac1
  - Disable discard store checks on NVIDIA Mac1
  - Disable image descriptor argument buffers for shader swizzle
  - Advertise native texture swizzle only on supported GPU families
  - Simplify NVIDIA workaround checks
  - Disable discarded-fragment store checks on Mac1 GPUs
  - Avoid fragile Mac1 argument-buffer descriptor cases
- Do not rely on extension enable for vkGetPhysicalDeviceToolPropertiesEXT proc addr
- Emulate reversed-depth viewports on affected AMD Mac GPUs
  - Refine legacy AMD Mac2 workaround handling
  - Scope discarded-fragment store checks to Apple GPUs
- Disable non-working quad control flow
- Disable 64-bit float control properties

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/17
2026-07-13 23:33:38 +00:00
KeatonTheBot 9580f20caa Update System.IO.Hashing to 10.0.9 2026-07-12 00:26:41 -05:00
Renovate Bot 152460929d Update dependency Microsoft.NET.Test.Sdk to 18.7.0 (#15)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) | `18.6.0` → `18.7.0` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Microsoft.NET.Test.Sdk/18.7.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Microsoft.NET.Test.Sdk/18.6.0/18.7.0?slim=true) |

---

### Release Notes

<details>
<summary>microsoft/vstest (Microsoft.NET.Test.Sdk)</summary>

### [`v18.7.0`](https://github.com/microsoft/vstest/releases/tag/v18.7.0)

#### What's Changed

- Add ARM64 msdia140.dll support to test platform packages by [@&#8203;jamesmcroft](https://github.com/jamesmcroft) in [#&#8203;15689](https://github.com/microsoft/vstest/pull/15689)
- Update System.Memory from 4.5.5 to 4.6.3 by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15706](https://github.com/microsoft/vstest/pull/15706)

#### New Contributors

- [@&#8203;jamesmcroft](https://github.com/jamesmcroft) made their first contribution in [#&#8203;15689](https://github.com/microsoft/vstest/pull/15689)

**Full Changelog**: <https://github.com/microsoft/vstest/compare/v18.6.0...v18.7.0>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/15
2026-07-10 22:20:04 +00:00
Renovate Bot 7a5554a17f Update dependency DynamicData to 9.4.33 (#14)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [DynamicData](https://github.com/reactiveui/DynamicData) | `9.4.31` → `9.4.33` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/DynamicData/9.4.33?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/DynamicData/9.4.31/9.4.33?slim=true) |

---

### Release Notes

<details>
<summary>reactiveui/DynamicData (DynamicData)</summary>

### [`v9.4.33`](https://github.com/reactivemarbles/DynamicData/releases/tag/9.4.33)

#### Fixes:

- [`9bc6b44`](https://github.com/reactiveui/DynamicData/commit/9bc6b443f4864a23977ddce1310edafc3f5449c3) Fix race in ExpireAfter when item is removed or updated before expiration fires ([#&#8203;1076](https://github.com/reactiveui/DynamicData/issues/1076)) [@&#8203;dwcullop](https://github.com/dwcullop)
- [`7b861d0`](https://github.com/reactiveui/DynamicData/commit/7b861d0e12d8ad02d288157c5dd10b0be5f78d6b) Fixed that the cache-land dynamic `.Filter()` operator relied on limited mutation of an internal `Dictionary<,>` during enumeration, a behavior that is not supported on target frameworks older than .NET Core 3.0. Additional logic to copy the keys to iterate, before mutation, should now be injected for builds targeting such older frameworks. ([#&#8203;1084](https://github.com/reactiveui/DynamicData/issues/1084)) [@&#8203;JakenVeina](https://github.com/JakenVeina)
- [`b93a6b9`](https://github.com/reactiveui/DynamicData/commit/b93a6b99333db814b9759b475a95d8a7ecec1c24) Fix docs and exception messages for Change ([#&#8203;1085](https://github.com/reactiveui/DynamicData/issues/1085)) [@&#8203;xackus](https://github.com/xackus)
- [`cc8b91e`](https://github.com/reactiveui/DynamicData/commit/cc8b91ea362097cd26339c3727da77e533e7802c) Fixed that the Build and Release action defined in release.yml was attempting to publish a bogus "empty.nupkg" file that exists in the normal build output of the DynamicData.Tests project, as a part of the EmptyFiles package, since the job was written to search for "\*.nupkg". ([#&#8203;1124](https://github.com/reactiveui/DynamicData/issues/1124)) [@&#8203;JakenVeina](https://github.com/JakenVeina)

#### Other:

- [`bf45445`](https://github.com/reactiveui/DynamicData/commit/bf45445e5e3ad68c8d421fce192653189799740f) Add AI Instruction Files for GitHub Copilot ([#&#8203;1078](https://github.com/reactiveui/DynamicData/issues/1078)) [@&#8203;dwcullop](https://github.com/dwcullop)
- [`12c1947`](https://github.com/reactiveui/DynamicData/commit/12c1947cd19e0149318827a42b6615c2313b6227) Overhaul XML documentation for ObservableCacheEx extension methods ([#&#8203;1080](https://github.com/reactiveui/DynamicData/issues/1080)) [@&#8203;dwcullop](https://github.com/dwcullop)
- [`b0d4c70`](https://github.com/reactiveui/DynamicData/commit/b0d4c701919e8fb9dabd291096fea2ec5f372fde) Update SwappableLock to support NET9+ Lock type ([#&#8203;1077](https://github.com/reactiveui/DynamicData/issues/1077)) [@&#8203;dwcullop](https://github.com/dwcullop)
- [`b966943`](https://github.com/reactiveui/DynamicData/commit/b966943fe78e51b88f2b426546f82610395d4ad5) Add DynamicAccessedMembers attributes to BindingListEx methods ([#&#8203;1087](https://github.com/reactiveui/DynamicData/issues/1087)) [@&#8203;daniel-lerch](https://github.com/daniel-lerch)
- [`28adb4f`](https://github.com/reactiveui/DynamicData/commit/28adb4fe1aef34ffa7d53f888af7a8f5bdefa690) Overhaul XML documentation for ObservableListEx extension methods ([#&#8203;1081](https://github.com/reactiveui/DynamicData/issues/1081)) [@&#8203;dwcullop](https://github.com/dwcullop)
- [`335ecbc`](https://github.com/reactiveui/DynamicData/commit/335ecbca2d300fdbfb9b2f40a1ffb95f00f46923) Add Preview workflows and enforce versioning rules ([#&#8203;1088](https://github.com/reactiveui/DynamicData/issues/1088)) [@&#8203;dwcullop](https://github.com/dwcullop)
- [`d59ed5c`](https://github.com/reactiveui/DynamicData/commit/d59ed5cc768f5df2859687ce23920ce5fb343509) Replace softprops/action-gh-release with gh CLI ([#&#8203;1092](https://github.com/reactiveui/DynamicData/issues/1092)) [@&#8203;dwcullop](https://github.com/dwcullop)
- [`767f273`](https://github.com/reactiveui/DynamicData/commit/767f2737c47c67990ba1876539e984598dcb851d) Restore stable 9.4 versioning on release/9.x ([#&#8203;1123](https://github.com/reactiveui/DynamicData/issues/1123)) [@&#8203;dwcullop](https://github.com/dwcullop)

#### Test:

- [`c1c312b`](https://github.com/reactiveui/DynamicData/commit/c1c312b3c044a5718ff8923d5a233f9e32f96b70) test: fix AsyncDisposeMany thread safety test timeout on CI ([#&#8203;1082](https://github.com/reactiveui/DynamicData/issues/1082)) [@&#8203;dwcullop](https://github.com/dwcullop)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/14
2026-07-10 22:19:31 +00:00
Renovate Bot d5e4dfae56 Update dependency Ryujinx.SDL3-CS to 2026.707.0 (#16)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Ryujinx.SDL3-CS](https://github.com/Kenji-NX/SDL3-CS) | `2026.501.0` → `2026.707.0` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Ryujinx.SDL3-CS/2026.707.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Ryujinx.SDL3-CS/2026.501.0/2026.707.0?slim=true) |

---

### Release Notes

<details>
<summary>Kenji-NX/SDL3-CS (Ryujinx.SDL3-CS)</summary>

### [`v2026.707.0`](https://github.com/Kenji-NX/SDL3-CS/compare/2026.501.0...2026.707.0)

[Compare Source](https://github.com/Kenji-NX/SDL3-CS/compare/2026.501.0...2026.707.0)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/16
2026-07-10 22:18:56 +00:00
Renovate Bot 28268ef7ef Update avalonia monorepo to 11.3.18 (#13)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Avalonia](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.17` → `11.3.18` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia/11.3.18?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia/11.3.17/11.3.18?slim=true) |
| [Avalonia.Desktop](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.17` → `11.3.18` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia.Desktop/11.3.18?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia.Desktop/11.3.17/11.3.18?slim=true) |
| [Avalonia.Diagnostics](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.17` → `11.3.18` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia.Diagnostics/11.3.18?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia.Diagnostics/11.3.17/11.3.18?slim=true) |
| [Avalonia.Markup.Xaml.Loader](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.17` → `11.3.18` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia.Markup.Xaml.Loader/11.3.18?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia.Markup.Xaml.Loader/11.3.17/11.3.18?slim=true) |

---

### Release Notes

<details>
<summary>AvaloniaUI/Avalonia (Avalonia)</summary>

### [`v11.3.18`](https://github.com/AvaloniaUI/Avalonia/releases/tag/11.3.18)

[Compare Source](https://github.com/AvaloniaUI/Avalonia/compare/11.3.17...11.3.18)

##### What's Changed

##### Enhancements

- XAML – Enhance Roslyn-compiler visible metadata by [@&#8203;maxkatz6](https://github.com/maxkatz6) in [#&#8203;21546](https://github.com/AvaloniaUI/Avalonia/pull/21546)

##### Fixes

- Core – Fix StackOverflow when a `NaN` offset is set on `ScrollViewer` by [@&#8203;NicholasLachapelle](https://github.com/NicholasLachapelle) in [#&#8203;21558](https://github.com/AvaloniaUI/Avalonia/pull/21558)
- macOS – Handle `replacementRange` in `AvnView` by [@&#8203;MrJul](https://github.com/MrJul) in [#&#8203;21608](https://github.com/AvaloniaUI/Avalonia/pull/21608)

**Full Changelog**: <https://github.com/AvaloniaUI/Avalonia/compare/11.3.17...11.3.18>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/13
2026-07-10 22:18:09 +00:00
LotPandLotP1 6c47892b94 mono-jit
- fix alignment issue with jitcache that could cause a crash.
- Reimplement sparse-jit to only use 1 layer.
    - the old 2 layer approach basically only ever used 1 path due to the bottom-layer size, so doing a double look-up seemed wasteful.
    - changed memory block allocation stategy to allocate based on requested code range instead of the possible address-space.

Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
2026-07-10 10:03:48 -05:00
KeatonTheBot b362f837c5 Fix MemoryMap/MemoryUnmap/MemoryProtect in UnicornAArch32 and UnicornAArch64 2026-07-10 10:03:48 -05:00
LotPandLotP1 a82a721df6 instanced jit cache
- jit caches are no longer static.
- JitUnwindWindows now supports multiple jit caches.
- Jit caches should no longer cause crashes due to entry offset collisions.
- cpu tests now run concurrently.

Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
2026-07-01 09:54:23 -05:00
ranidspace 297df259c3 Remove unused windowing library package
The `OpenTK.Windowing.GraphicsLibraryFramework` package is included in the Ryujinx csproj, but is never used in the project.

Checking the source code, all source files included in the package are part of the `OpenTK.Windowing.GraphicsLibraryFramework` namespace, which is never included in the Ryujinx source code. Rider also confirms that it's an unused reference.

The library includes the entirety of libglfw into the program, it clears about a half a megabyte. Not much, but it's free.
2026-07-01 09:54:22 -05:00
LotP 3ace5deadf fix-tests
- downgrade unicorn to last working version
- update to new cp reg struct system
- remove nonexistent register (used to silently continue)
- fix partial unmap tests
    - InitializeSignalHandler() was moved out of the translator (almost 2.5 years ago), but the test code was never updated to manually call the function as it was changed to do in the real cpu context, so the tests just started failing.
    - by manually initializing the handler we no longer cause tests to fail.
2026-07-01 09:54:22 -05:00
_Neo_ 04aacb1385 macOS: Fix Hypervisor BadArgument Launch Crash
Ayyyy, welcome to the **macOS: Fix Hypervisor BadArgument Launch Crash PR!**

We are so so back my friends.

This PR fixes the macOS Hypervisor `BadArgument` launch crash, while also sneaking in some improvements to help _potentially_ mitigate those lovely 0 FPS deadlock on macOS in certain games with Vulkan + Hypervisor enabled (there will be a separate PR in the future to address this).

The PR is slightly different in terms of the PR messages that I write, but I hope that it'll provide the necessary context to those unaware of the reasoning/causes of the issue.

Also, this is a bit of a read, so grab some snacks, pour a drink, get comfortable, and enjoy some…

### STORY TIME!!!

_Before we proceed forward, we must properly understand what Hypervisor is._

_Hypervisor refers to using Apple's built-in virtualization technology, the Hypervisor Framework, to accelerate CPU emulation._

_There are two main ways to do this:_

_1. **Interpreter** – executes instructions one by one (very slow)._
_2. **JIT (Just-In-Time)** recompilation – translates Switch CPU instructions into native code (much faster)._

_On Apple Silicon Macs (M1, M2, M3, etc.), the guest CPU and host CPU are both ARM64-based. As the Nintendo Switch itself is ARM64, The Hypervisor Framework can allow Ryujinx to execute large amounts of guest code more directly, reducing emulation overhead. The architectural similarity makes hardware-assisted execution practical. On Intel Macs, there isn't the same direct ARM-to-ARM advantage, so the benefits are much more limited or unavailable._

_When Hypervisor mode is available and working correctly, it can provide higher frames, reduced CPU bottlenecks, faster shader compilation in certain instances, smoother gameplay, and lower CPU usage._

_The improvement depends heavily on the game. CPU-heavy titles tend to benefit the most._

_There are downsides though. Sometimes, certain games may have compatibilities issues, certain feature may not work exactly the same, bugs in the actual Hypervisor implementation can cause crashes or issues, and performance gain vary significantly on a game-to-game basis._

_In terms of a simple analogy:_

_**Without Hypervisor:** Ryujinx acts like a translator who rewrites every sentence before speaking it._
_**With Hypervisor:** Ryujinx can let the Mac's CPU understand and execute much more of the Switch's code directly, so less translation work is needed._

_Anyway, now that we have the background, onto the story!_

Once upon a time, around ~December 2025 / January 2026, users — and yes, even devs (though let’s be honest, macOS devs are basically cryptids at this point)

_(Authors note: Cryptids - animals or other beings whose present existence is disputed or unsubstantiated by science)_

Started noticing inconsistent behaviour with the macOS Hypervisor. In other words, Hypervisor began showing inconsistent launch behaviour for users who've upgraded to the then current macOS versions (15.7.3 / 26.2). This behaviour continued as macOS got updated, and is still present on the latest version (Tahoe 26.5.1).

Initially, Hypervisor was mostly crashing with an `Unexpected result "Denied"`. The likely cause is two fold:
1. macOS security permissions changed during that update.
2. Hypervisor framework got updated, but nothing what communicated to the devs.

(It is likely to be the former, not the later. This is the main theory, at least.)

However, this crash later evolved into (because of course it did) into `Unexpected result "BadArgument"`.

We'll come back to this in a bit.

For users who wished to run games with Hypervisor enabled, they had a simple solution - build Ryujinx locally. Yes, this is an effective solution in its own right, as that does bypass certain macOS signing and certain .app bundle-related constraints, as well as certain security permissions.

However, this doesn't explain why:
* Official server builds were the only builds that were affected
* Users on _older macOS_ (below 15.7.3 / 26.2) could run games with Hypervisor enabled as if nothing had ever gone wrong in their lives.
* Users on _newer macOS_ versions _could still run certain older_ Ryujinx builds as intended — specifically, the last or close-to-last version they had lying around in their Downloads folder.
* Users on macOS above 15.7.3 / 26.2 could still run games with Hypervisor enabled, on builds downloaded from the server, for whatever reason.

So now we’re here:
* Same macOS versions → Different outcomes
* Different Ryujinx builds → Different behavior
* `Denied` → Evolved into `BadArgument`
* No consistent reproduction steps

And so, after attempting to fix this for quite some time, I've determined it to be unfixable. Ryujinx should drop macOS support because development has been held back far too long, and there is nothing we can possibly do. Every possible solution was tested and the results were null.

So, it is with great sadness that, here and now, I proclaim that Ryujinx will terminate further macOS support.

Bye-bye, Ryujinx on macOS! You had a great run, and you'll always be remembered. Thank you for bringing so much great memories to us, macOS users!

...

...

...

...

...

HAHA - NOT SO FAST!

(_Please tell me I got you there_)

Not all hope is lost - there is light at the end of the tunnel!

**The key insight is this:** `BadArgument` is not just “`Denied` but newer and worse”. It’s something else entirely.

* `Denied` → macOS refuses the request at the security boundary. Nothing even starts.
* `BadArgument` → macOS accepts the request, but the data being passed is now invalid under newer constraints.

So instead of:

_“You shall not pass.”_

We now have:

_“You may pass… but what is THAT?”_

And this brings us to this PR!

### The PR

Users on macOS were experiencing two major issues when Hypervisor was enabled:

**1. Hard Crash – "Unexpected result `BadArgument`"**

The most frequent stack trace pointed to:

```
System.Exception: Unexpected result "BadArgument".
   at Ryujinx.Cpu.AppleHv.HvResultExtensions.ThrowOnError(...)
   at Ryujinx.Cpu.AppleHv.HvExecutionContextVcpu.GetX(Int32 index)
   at Ryujinx.HLE.HOS.Kernel.Threading.KThread.GetCurrentContext()
   at Ryujinx.HLE.HOS.Kernel.Threading.KThread.GetThreadContext3(...)
```
This crash occurred very early during game initialization, most often inside supervisor calls such as `GetThreadContext3`.

**2. Intermittent Permanent 0 FPS Freezes with Vulkan (unrelated to `BadArgument`, but somewhat addressed in this PR)**
The game would run normally for a while, then drop to 0 FPS permanently. Disabling Hypervisor eliminated the freezes but performance was at times inconsistent.

### The Explanation

`HvResult.BadArgument` is returned by Apple's Hypervisor framework (`hv_vcpu_get_reg`, `hv_vcpu_get_sys_reg`, `hv_vcpu_get_simd_fp_reg`, etc.) when it determines that the requested operation cannot be performed in the current vCPU state.

**Common reasons why this happens:**
* Timing races - the guest tries to read registers before the vCPU has fully synchronized its internal state after context switches, exceptions, or vCPU pool reuse.
* Stricter validation in newer macOS versions.
* vCPU state inconsistencies during early boot or heavy syscall activity.

Because the original implementation called `.ThrowOnError()` unconditionally on every Hypervisor call, even a single transient `BadArgument` would immediately terminate execution. This was the direct cause of the crash.

The 0 FPS issue was a separate but related symptom: when Hypervisor is enabled, the guest CPU runs significantly faster than MoltenVK's presentation queue expects, leading to command buffer starvation and queue deadlock.

### The Changes

* **Implemented: Complete Shadow Register Cache (`HvExecutionContextVcpu.cs`)**
    * Added full in-memory shadow copies of every relevant register:
        * General-purpose registers: `private readonly ulong[] _x = new ulong[32]`;
        * Vector/SIMD registers: private readonly `V128[] _v = new V128[32]`;
        * System/special registers: `_pc`, `_elrEl1`, `_esrEl1`, `_tpidrEl0`, `_tpidrroEl0`, `_fpcr`, `_fpsr`, and `_pstateRaw` (stored as `ulong` internally to avoid type conversion issues).
    * **Reasoning:** Returning zero or garbage values on failure could corrupt game state. When the Hypervisor returns `BadArgument`, we now have a safe, previously-valid value to return instead of crashing. A shadow cache gives us that last known good value. The cache is updated on every successful read, so correctness is preserved in normal operation. This is the central fix for the `BadArgument` crash.

* **Defensive BadArgument Handling on Every Register Access Path**
    * Updated every read operation (`GetX`, `GetV`, `Pstate`, cached getters, etc.) to explicitly check the return value, with the same pattern applied across all accessors.
    * **Reasoning:** This directly eliminates the crash while preserving the original error-throwing behavior for genuine failures. Returning cached data is far safer than injecting zero or garbage values that could corrupt guest state:

```
HvResult res = HvApi.hv_vcpu_get_...(..., out value);
if (res == HvResult.BadArgument)
{
    _fallbackCount++;
    LogHvWarning("...");           // rate-limited
    return cachedValue;            // safe fallback
}
res.ThrowOnError();                // only throw on real errors
return cachedValue = value;        // success path + cache update
```

* **AggressiveMode Global Option** (NOT IMPLEMENTED IN THIS PR, BUT A FUTURE PR WILL ADD THIS FULLY)
    * Added:
`public static bool AggressiveMode { get; set; } = false;`
    * When true/enabled, all warning logs in LogHvWarning() are suppressed.
    * **Reasoning:** Diagnostic logging has overhead. In the future, users will be able to disable/enable this when required. This will also be useful for any dev working on macOS Hypevisor (aka - me).

* **Hot-Path Optimizations**
    * Changed index checks from `if (index < 0 || index > 30)` to `if ((uint)index > 30)` in `GetX`, `SetX`, `GetV`, and `SetV`.
    * Separated cache initialization into a dedicated `InitializeCacheDefaults()` method called by constructor and `Reset()`.
    * **Reasoning:** `GetX` is called millions of times per second. Reducing branches improves CPU branch prediction and overall throughput. The clean initialization pattern ensures reliable behavior when vCPUs are reused from the pool.

* **Improved Logging, Diagnostics & Monitoring**
    * Rate-limited warnings with a cooldown timer (`WarningCooldownTicks = 1_000_000_000 ≈ 1 second`).
    * Clear, descriptive messages (e.g. "PAC failure on SP_EL0", "PAC failure on X15").
    * Added `public long GetFallbackCount()` for runtime monitoring.
    * **Reasoning:** We need visibility into fallback frequency during development and testing, but we must not spam the log for end users during normal gameplay.

_Quick side note:_

_The logging mentions "PAC failure" rather than HV `BadArgument`, and this is a shorthand I've added right now for myself.
Technically, we are catching HvResult.BadArgument. It's called "PAC failure" in logs because PAC is a frequent trigger, especially with games like TOTK that use heavy mods (AKA TOTK Optimiser)._

_Right now, I'm calling it a PAC failure, but will later rename to `BadArgument` as I progress further on a certain issue that's been sitting in our GitHub for some time._

_Back to the main stuff:_

* **GPU Synchronization & _Potential_ 0 FPS Mitigation (`HvExecutionContext.cs`) (PR FOLLOW UP IN THE FUTURE)**
    * Added adaptive `TryGpuSync()` called periodically in the main `Execute()` loop (every 10–12 execution steps).
    * Added strategic `Thread.Yield()` after `SvcAarch64` handling and in sync points.
    * Tuned counters based on real gameplay testing (% 10 for light sync, % 6 for stronger sync).
    * **Reasoning:** Hypervisor accelerates the guest CPU significantly. Without periodic yielding/flushing, MoltenVK's presentation queue can starve, causing permanent 0 FPS. The adaptive approach gives aims to fix this by performing a "resync". This has been tested and is showing positive progress, but, as mentioned earlier, will continue in a future PR.

These are all of the changes in this PR.

It's been quite a ride getting Hypervisor back up and running, but I loved every second of it.

If there is anything that is unclear in this PR, please let me know and I'll provide more details and update the description above.

Here is a small FAQ section to answer some more questions. These answers come from my own personal testing and behaviour that I observed.

### FAQ
**Q:** Why can some users play games with Hypervisor enabled and others can't on newer macOS versions?
**A:** This needs a bit of clarification.
You _can_ still run games with Hypervisor enabled on latest macOS - just not all games. Games such as Pokémon Legends: Z-A, The Legend of Zelda: Tears of the Kingdom, The Legend of Zelda: Breath of the Wild, can all _run_ and will _boot_ with Hypervisor enabled.

However, some other games, like Beyblade X Xone, Beyblade X Evobattle, Tales of Xillia Remastered, Tales of Berserk Remastered, etc, will _crash_ on boot with `BadArgument` error.

So it's not that Hypervisor works for one user and doesn't work for the other.

_It's that the game doesn't cause Hypervisor to crash._

Games differ in how aggressively they read registers early in boot and how much they rely on modded/JIT code. Games with heavy early supervisor calls and/or mods that manipulate pointers/PAC (pointer authentication errors) are far more likely to hit BadArgument.

(PAC is the reason why TOTK crashes with UltraCam when Hypervisor is disabled on macOS)

Lightweight or less modded titles often avoid the problematic code paths.
––––––
**Q:** How stable is this fix?
**A:** Updating macOS versions (Tahoe -> Tahoe) didn't break this implementation. Current data and testing all provide positive results (i.e. - no crash, hooray!)
––––––
**Q:** What about the `Denied` error?
**A:** This has more so to do with macOS permissions. Currently, Ryujinx is not being shipped in a DMG (which, by Apple conventions, is a more proper way of handling macOS app installations outside of the App Store). There is a theory that packaging Ryujinx into a DMG file will alleviate any permission issues that we currently have, and may help fix this type of error, so this PR may not even be needed (but we'll have to see how it goes).

That said, this error has not appeared in any of the more recent crash logs that I've analysed or acquired myself (from testing), leading to a possible hypothesis that the permission issues have been resolved, but the Hypervisor implementation broke on server builds due to an update to the framework. This is, however, unlikely, because, again, only builds after a particular version (i.e. - the last working version on a users system) broke.

However, DMG packaging is still on a table and something that we want to implement as soon as possible.
––––––
**Q:** So why do local builds run then?
**A:** A lot of different factors - build environment, folder location, lack of permissions associated with running .exe files, different ways of handling pure .exe files and .app bundles, notarisation, and more. There was no "scientific testing" done on each of the factors and seeing which one affects what exactly. Pushing this PR and implementing the DMG will tell.
2026-06-27 14:27:57 -05:00
Renovate Bot ea91841e9b Update dependency Ryujinx.Graphics.Vulkan.MoltenVK to 1.4.2-ryujinx.4 (#12)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Ryujinx.Graphics.Vulkan.MoltenVK](https://github.com/Kenji-NX/MoltenVK) | `1.4.2-ryujinx.3` → `1.4.2-ryujinx.4` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Ryujinx.Graphics.Vulkan.MoltenVK/1.4.2-ryujinx.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Ryujinx.Graphics.Vulkan.MoltenVK/1.4.2-ryujinx.3/1.4.2-ryujinx.4?slim=true) |

---

### Release Notes

<details>
<summary>Kenji-NX/MoltenVK (Ryujinx.Graphics.Vulkan.MoltenVK)</summary>

### [`v1.4.2-ryujinx.4`](https://github.com/Kenji-NX/MoltenVK/releases/tag/v1.4.2-ryujinx.4)

##### Fork changes

- Revert "MVKCommandBuffer: Optimize management of command storage"

##### Upstream changes

- Add gl\_DrawID / DrawIndex support for MacOS
- Draw ID fix-ups for serialization and formatting
- Improve handling of draw ID buffer binding
- Update SPIRV-Cross to latest revision
- Do not enable blending when unsupported by format

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNzguMCIsInVwZGF0ZWRJblZlciI6IjQzLjE3OC4wIiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/12
2026-06-19 04:17:44 +00:00
Babib3l 5734ad9090 Fix Windows console hide path targeting the foreground window
This PR addresses [Ryubing/Issues#345](https://github.com/Ryubing/Issues/issues/345) by fixing the Windows console hide/show path so it only acts on Ryujinx’s own console window instead of whatever window happens to be focused during startup. Previously, when Show Console was disabled, the helper could race with focus changes and end up affecting another app or shell window while leaving the console visible; this change removes that foreground-window dependency and keeps the startup behavior scoped to the Ryujinx console.
2026-06-14 23:18:49 -05:00
Renovate Bot 1486d2db40 Update dependency Microsoft.NET.Test.Sdk to 18.6.0 (#11)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) | `18.5.1` → `18.6.0` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Microsoft.NET.Test.Sdk/18.6.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Microsoft.NET.Test.Sdk/18.5.1/18.6.0?slim=true) |

---

### Release Notes

<details>
<summary>microsoft/vstest (Microsoft.NET.Test.Sdk)</summary>

### [`v18.6.0`](https://github.com/microsoft/vstest/releases/tag/v18.6.0)

#### What's Changed

- Revert removal of Video Recorder by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15336](https://github.com/microsoft/vstest/pull/15336)
- Speed up blame by filtering non-.NET processes from dump collection by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15518](https://github.com/microsoft/vstest/pull/15518)
- Add README.md to NuGet packages by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15550](https://github.com/microsoft/vstest/pull/15550)
- Report child process info on connection timeout by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15603](https://github.com/microsoft/vstest/pull/15603)

##### Changes to tests and infra

- Brand as 18.6 by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15423](https://github.com/microsoft/vstest/pull/15423)
- Upgrading code coverage version to 18.5.1, by [@&#8203;fhnaseer](https://github.com/fhnaseer) in [#&#8203;15422](https://github.com/microsoft/vstest/pull/15422)
- Updating System.Collections.Immutable to 9.0.11 by [@&#8203;MSLukeWest](https://github.com/MSLukeWest) in [#&#8203;15425](https://github.com/microsoft/vstest/pull/15425)
- Fix attachVS when used for debugging integration tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15451](https://github.com/microsoft/vstest/pull/15451)
- Replace dotnet.config, with global.json by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15449](https://github.com/microsoft/vstest/pull/15449)
- Document debugging integration tests with AttachVS by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;15452](https://github.com/microsoft/vstest/pull/15452)
- Fix stack overflow tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15461](https://github.com/microsoft/vstest/pull/15461)
- Make TestAssets.sln buildable locally by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15466](https://github.com/microsoft/vstest/pull/15466)
- Try filtering out tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15463](https://github.com/microsoft/vstest/pull/15463)
- Build just once when tfms run in parallel by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15465](https://github.com/microsoft/vstest/pull/15465)
- Review simplify compatibility sources, deduplicate tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15472](https://github.com/microsoft/vstest/pull/15472)
- Cleanup dead TRX code by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15474](https://github.com/microsoft/vstest/pull/15474)
- Update .NET runtimes to 8.0.25, 9.0.14, and 10.0.4 by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15481](https://github.com/microsoft/vstest/pull/15481)
- Compat matrix checker by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15480](https://github.com/microsoft/vstest/pull/15480)
- Add trx analysis skill by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15486](https://github.com/microsoft/vstest/pull/15486)
- Split integration tests to single tfm and multi tfm project by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15484](https://github.com/microsoft/vstest/pull/15484)
- Update matrix by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15477](https://github.com/microsoft/vstest/pull/15477)
- Break infinite restore loop in VS by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15503](https://github.com/microsoft/vstest/pull/15503)
- Use global package cache for build, and local for running integration tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15500](https://github.com/microsoft/vstest/pull/15500)
- Update contributing by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15505](https://github.com/microsoft/vstest/pull/15505)
- Reduce test wall-clock time by increasing minThreads by [@&#8203;drognanar](https://github.com/drognanar) in [#&#8203;15502](https://github.com/microsoft/vstest/pull/15502)
- Indicator flakiness by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15513](https://github.com/microsoft/vstest/pull/15513)
- Fix ci build by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15515](https://github.com/microsoft/vstest/pull/15515)
- Fix thread safety issues by [@&#8203;Evangelink](https://github.com/Evangelink) in [#&#8203;15512](https://github.com/microsoft/vstest/pull/15512)
- Optimize DotnetSDKSimulation\_PostProcessing test (163s → 61s) by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15516](https://github.com/microsoft/vstest/pull/15516)
- Build isolated test assets for single TFM instead of 7 by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15517](https://github.com/microsoft/vstest/pull/15517)
- Remove unused dependencies from Library.IntegrationTests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15527](https://github.com/microsoft/vstest/pull/15527)
- Remove printing \_attachments content to console by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15520](https://github.com/microsoft/vstest/pull/15520)
- Add Linux/macOS test filtering guide to CONTRIBUTING.md by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15521](https://github.com/microsoft/vstest/pull/15521)
- Change integration test parallelization from ClassLevel to MethodLevel by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15526](https://github.com/microsoft/vstest/pull/15526)
- Unify target framework checks with IsNetFrameworkTarget/IsNetTarget by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15523](https://github.com/microsoft/vstest/pull/15523)
- Add unattended work instructions to copilot-instructions.md by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15531](https://github.com/microsoft/vstest/pull/15531)
- Reduce code style rule severity from warning to suggestion by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15522](https://github.com/microsoft/vstest/pull/15522)
- Remove Debug/Release line number branching from tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15519](https://github.com/microsoft/vstest/pull/15519)
- Revise unattended work instructions in copilot-instructions.md by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15532](https://github.com/microsoft/vstest/pull/15532)
- Improve CompatibilityRowsBuilder error message with diagnostic details by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15529](https://github.com/microsoft/vstest/pull/15529)
- docs: add git worktree and upstream sync workflow to copilot-instructions.md by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15538](https://github.com/microsoft/vstest/pull/15538)
- Add VSIX runner to smoke tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15541](https://github.com/microsoft/vstest/pull/15541)
- Remove deprecated WebTest and TMI test methods by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15525](https://github.com/microsoft/vstest/pull/15525)
- Fix compatibility test failures for legacy vstest.console and MSTest adapter by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15534](https://github.com/microsoft/vstest/pull/15534)
- Convert TestPlatform.sln to slnx format by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15551](https://github.com/microsoft/vstest/pull/15551)
- Convert test/TestAssets .sln files to .slnx format by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15557](https://github.com/microsoft/vstest/pull/15557)
- Enable parallelization for blame data collector tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15552](https://github.com/microsoft/vstest/pull/15552)
- Fix CI failure when GeneratedTestAssets directory doesn't exist by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15556](https://github.com/microsoft/vstest/pull/15556)
- Set DOTNET\_ROOT in test.sh for local Linux usage by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15559](https://github.com/microsoft/vstest/pull/15559)
- Use MSTest recommended analyzers by [@&#8203;Evangelink](https://github.com/Evangelink) in [#&#8203;15539](https://github.com/microsoft/vstest/pull/15539)
- Document semicolon handling in RunSettings test parameters by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15561](https://github.com/microsoft/vstest/pull/15561)
- Enable CA1067 analyzer and fix violations by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15560](https://github.com/microsoft/vstest/pull/15560)
- Fix HTML logger parallel file collision with atomic file creation by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15562](https://github.com/microsoft/vstest/pull/15562)
- Deduplicate package extraction between verify-nupkgs and IntegrationTestBuild by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15554](https://github.com/microsoft/vstest/pull/15554)
- Fix MSTEST0046: use Assert.MatchesRegex instead of StringAssert.Matches by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15575](https://github.com/microsoft/vstest/pull/15575)
- Attach diagnostic logs to acceptance test runs by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15572](https://github.com/microsoft/vstest/pull/15572)
- Deprecate EnableShutdownAfterTestRun which is no-op by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15576](https://github.com/microsoft/vstest/pull/15576)
- Skip VideoRecorder test on CI due to access denied errors by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15587](https://github.com/microsoft/vstest/pull/15587)
- Fix integration test build collision with mutex + EventWaitHandle by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15568](https://github.com/microsoft/vstest/pull/15568)
- Reduce blame test flakiness: increase hang dump timeout to 10s by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15590](https://github.com/microsoft/vstest/pull/15590)
- Fix concurrent modification in MetricsCollection by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15581](https://github.com/microsoft/vstest/pull/15581)
- Fix PassingNoArguments test: disable --diag to preserve help output by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15583](https://github.com/microsoft/vstest/pull/15583)
- perf: fix dictionary double-lookups, Collection.Contains, and LINQ allocations by [@&#8203;Evangelink](https://github.com/Evangelink) in [#&#8203;15533](https://github.com/microsoft/vstest/pull/15533)
- Replace VSSDK-sourced DLLs with proper package references by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15567](https://github.com/microsoft/vstest/pull/15567)
- Add target framework to default TRX file name by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15565](https://github.com/microsoft/vstest/pull/15565)
- Update post-build template parameters by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15591](https://github.com/microsoft/vstest/pull/15591)
- Fix path for post-build template and adjust validation by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15592](https://github.com/microsoft/vstest/pull/15592)
- Add azure-pipelines-official.yml to pipeline files by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15594](https://github.com/microsoft/vstest/pull/15594)
- Update comment formatting for signing validation by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15597](https://github.com/microsoft/vstest/pull/15597)
- Fix enable-auto-merge for maestro by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15595](https://github.com/microsoft/vstest/pull/15595)
- Auto-approve maestro PRs by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15598](https://github.com/microsoft/vstest/pull/15598)
- Update enable-auto-merge to squash by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15602](https://github.com/microsoft/vstest/pull/15602)
- Update enable-auto-merge.yml for the correct permissions by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15606](https://github.com/microsoft/vstest/pull/15606)
- Add 365 regression tests for untested bug fixes by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15615](https://github.com/microsoft/vstest/pull/15615)
- Fix typos and add comments to empty catch blocks by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15609](https://github.com/microsoft/vstest/pull/15609)
- Fix flaky EventLogCollector test: ensure deterministic event log entries by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15607](https://github.com/microsoft/vstest/pull/15607)
- Mark PathConverter tests as Windows-only by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15617](https://github.com/microsoft/vstest/pull/15617)
- Fix HangDumpOnTimeout flakiness and ignore VideoRecorder test by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15616](https://github.com/microsoft/vstest/pull/15616)
- Add copilot-setup-steps.yml by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15604](https://github.com/microsoft/vstest/pull/15604)
- Add CreateNoNewWindow RunConfiguration setting by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15585](https://github.com/microsoft/vstest/pull/15585)
- Add preview packages documentation by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15628](https://github.com/microsoft/vstest/pull/15628)
- Cleanup filter implementation by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15629](https://github.com/microsoft/vstest/pull/15629)
- Fix SCI binding failure in DTA hosts (rel/18.6) by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15722](https://github.com/microsoft/vstest/pull/15722)
- Remove DiagnosticSource binding redirect (rel/18.6) by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15776](https://github.com/microsoft/vstest/pull/15776)

**Full Changelog**: <https://github.com/microsoft/vstest/compare/v18.5.1...v18.6.0>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNzguMCIsInVwZGF0ZWRJblZlciI6IjQzLjE3OC4wIiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/11
2026-06-13 05:10:20 +00:00
Renovate Bot c54ca28843 Update dependency Microsoft.IdentityModel.JsonWebTokens to 8.19.1 (#10)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) | `8.18.0` → `8.19.1` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Microsoft.IdentityModel.JsonWebTokens/8.19.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Microsoft.IdentityModel.JsonWebTokens/8.18.0/8.19.1?slim=true) |

---

### Release Notes

<details>
<summary>AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet (Microsoft.IdentityModel.JsonWebTokens)</summary>

### [`v8.19.1`](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/HEAD/CHANGELOG.md#8191)

[Compare Source](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.19.0...8.19.1)

\====

#### Bug Fixes

- Update `JwtSecurityTokenHandler` for `IssuerSigningKeyResolverUsingConfiguration` to take priority over `IssuerSigningKeyResolver`, matching the documented contract and the correct behavior already present in `JsonWebTokenHandler`. See [PR #&#8203;3519](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3519).

### [`v8.19.0`](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/HEAD/CHANGELOG.md#8190)

[Compare Source](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.18.0...8.19.0)

\====

#### New Features

- Add ML-DSA (FIPS 204) post-quantum signature support. See [PR #&#8203;3479](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3479).
- Cache custom crypto providers in CryptoProviderFactory. See [PR #&#8203;3489](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3489).

#### Bug Fixes

- Disable automatic redirects on default HttpClient for JKU retrieval. See [PR #&#8203;3494](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3494).
- Adjust rented buffer handling in claim set parsing. See [PR #&#8203;3493](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3493).
- Tidy null handling in SAML conditions validation. See [PR #&#8203;3491](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3491).
- Improve validation of `jku` claim. See [PR #&#8203;3481](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3481).
- Limit telemetry algorithm dimension cardinality. See [PR #&#8203;3490](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3490).
- Add defensive copy of collections in ValidationParameters. See [PR #&#8203;3492](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3492).
- Update TokenValidationParameter copy constructor to make a deep copy. See [PR #&#8203;3488](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3488).
- Update to fail-closed when replay protection isn't configured and other DPoP hardening. See [PR #&#8203;3505](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3505).
- Apply RFC 3986 section 6.2.2 normalization to DPoP `htu` comparison. See [PR #&#8203;3509](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3509).

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNzguMCIsInVwZGF0ZWRJblZlciI6IjQzLjE3OC4wIiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/10
2026-06-13 05:04:50 +00:00
Renovate Bot a53e691185 Update skiasharp monorepo to 3.119.4 (#9)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [SkiaSharp](https://go.microsoft.com/fwlink/?linkid=868515) | `3.119.2` → `3.119.4` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/SkiaSharp/3.119.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/SkiaSharp/3.119.2/3.119.4?slim=true) |
| [SkiaSharp.NativeAssets.Linux](https://go.microsoft.com/fwlink/?linkid=868515) | `3.119.2` → `3.119.4` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/SkiaSharp.NativeAssets.Linux/3.119.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/SkiaSharp.NativeAssets.Linux/3.119.2/3.119.4?slim=true) |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNzguMCIsInVwZGF0ZWRJblZlciI6IjQzLjE3OC4wIiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/9
2026-06-13 05:04:29 +00:00
Renovate Bot bf8c902d13 Update dependency Ryujinx.Audio.OpenAL to 1.25.2 (#8)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| Ryujinx.Audio.OpenAL | `1.25.1` → `1.25.2` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Ryujinx.Audio.OpenAL/1.25.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Ryujinx.Audio.OpenAL/1.25.1/1.25.2?slim=true) |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNzguMCIsInVwZGF0ZWRJblZlciI6IjQzLjE3OC4wIiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/8
2026-06-13 05:03:51 +00:00
Renovate Bot f4dc400a47 Update avalonia monorepo to 11.3.17 (#7)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Avalonia](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.16` → `11.3.17` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia/11.3.17?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia/11.3.16/11.3.17?slim=true) |
| [Avalonia.Desktop](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.16` → `11.3.17` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia.Desktop/11.3.17?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia.Desktop/11.3.16/11.3.17?slim=true) |
| [Avalonia.Diagnostics](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.16` → `11.3.17` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia.Diagnostics/11.3.17?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia.Diagnostics/11.3.16/11.3.17?slim=true) |
| [Avalonia.Markup.Xaml.Loader](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.16` → `11.3.17` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia.Markup.Xaml.Loader/11.3.17?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia.Markup.Xaml.Loader/11.3.16/11.3.17?slim=true) |

---

### Release Notes

<details>
<summary>AvaloniaUI/Avalonia (Avalonia)</summary>

### [`v11.3.17`](https://github.com/AvaloniaUI/Avalonia/releases/tag/11.3.17)

[Compare Source](https://github.com/AvaloniaUI/Avalonia/compare/11.3.16...11.3.17)

##### What's Changed

##### Performance

- XAML – Improve type caching in the XAML compiler by [@&#8203;MrJul](https://github.com/MrJul) in [#&#8203;21408](https://github.com/AvaloniaUI/Avalonia/pull/21408)

**Full Changelog**: <https://github.com/AvaloniaUI/Avalonia/compare/11.3.16...11.3.17>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNzguMCIsInVwZGF0ZWRJblZlciI6IjQzLjE3OC4wIiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/7
2026-06-13 05:03:21 +00:00
LotPandLotP1 a36882ab51 check for HandleDesc
HandleDesc can be null, make sure to check for that.

Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
2026-05-30 21:27:05 -05:00
KeatonTheBot 76a123b81d misc: chore: Use explicit types & fix object creation 2026-05-30 16:26:50 -05:00
KeatonTheBot 9d3fecf334 Revert "Gpu: exclude RGBA16Float texture format for fast DMA copy on Apple silicon."
This reverts commit b16f74ffd9.
2026-05-27 17:41:51 -05:00
KeatonTheBot 31d0bc5915 Revert "Shader: skip initialization of all clip distances on macOS."
This reverts commit f764ae2d58.
2026-05-27 17:41:47 -05:00
KeatonTheBotandsunshineinabox 30dba1ed36 macOS: Fix black screen on Metal versions older than 3.1
Fixes a black screen on Metal versions older than 3.1 by disabling `VK_EXT_extended_dynamic_state` (and `VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE`). Fix imported from sunshineinabox's unpublished extended dynamic states branch.

Co-authored-by: sunshineinabox <aqemail@gmail.com>
2026-05-27 11:51:44 -05:00
Xam 0d6c0aeeab HLE: Applets: Software keyboard: update cursor position on SetInputText
fixes cursor being at 0 position even when text is present, especially annoying for handheld devices with kde virtual keyboard, which is pretty bad, with no way to move the cursor or delete text backward
2026-05-27 10:27:15 -05:00
647 changed files with 8924 additions and 6548 deletions
+19 -20
View File
@@ -3,25 +3,25 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.3.16" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.16" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.16" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.16" />
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.5" />
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.5" />
<PackageVersion Include="Avalonia" Version="12.1.2" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="12.1.2" />
<PackageVersion Include="Avalonia.Desktop" Version="12.1.2" />
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="12.1.2" />
<PackageVersion Include="Svg.Controls.Avalonia" Version="12.0.0.17" />
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="12.0.0.17" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageVersion Include="Concentus" Version="2.2.2" />
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
<PackageVersion Include="DynamicData" Version="9.4.31" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.5.1" />
<PackageVersion Include="DynamicData" Version="9.4.33" />
<PackageVersion Include="FluentAvaloniaUI" Version="3.1.0" />
<PackageVersion Include="Gommon" Version="2.8.1.2" />
<PackageVersion Include="Humanizer" Version="3.0.10" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.18.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.22.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.10.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
@@ -31,25 +31,24 @@
<PackageVersion Include="OpenTK.Core" Version="4.9.4" />
<PackageVersion Include="OpenTK.Graphics" Version="4.9.4" />
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.9.4" />
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.9.4" />
<PackageVersion Include="Open.NAT.Core" Version="2.1.0.5" />
<PackageVersion Include="Ryujinx.Audio.OpenAL" Version="1.25.1" />
<PackageVersion Include="Ryujinx.Audio.OpenAL" Version="1.25.2" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Linux" Version="6.1.4-build6" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.macOS" Version="5.0.3-build14" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Windows" Version="6.1.4-build6" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.MoltenVK" Version="1.4.2-ryujinx.3" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.MoltenVK" Version="1.4.3-ryujinx.1" />
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.133" />
<PackageVersion Include="Ryujinx.SDL3-CS" Version="2026.501.0" />
<PackageVersion Include="Ryujinx.SDL3-CS" Version="2026.707.0" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Shaderc" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.23.0" />
<PackageVersion Include="SkiaSharp" Version="3.119.2" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.2" />
<PackageVersion Include="SkiaSharp" Version="3.119.4" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
<PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="10.0.8" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.1.4-a40db6c" />
<PackageVersion Include="System.IO.Hashing" Version="10.0.12" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.1.0" />
</ItemGroup>
</Project>
</Project>
+1
View File
@@ -21,6 +21,7 @@
<Project Path="src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj" />
<Project Path="src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj" />
<Project Path="src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj" />
<Project Path="src/Ryujinx.Graphics.RenderDocApi/Ryujinx.Graphics.RenderDocApi.csproj" />
<Project Path="src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj" />
<Project Path="src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj" />
<Project Path="src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj" />
@@ -13,13 +13,13 @@ namespace ARMeilleure.CodeGen.Arm64
public static void RunPass(ControlFlowGraph cfg)
{
var constants = new Dictionary<ulong, Operand>();
Dictionary<ulong, Operand> constants = new();
Operand GetConstantCopy(BasicBlock block, Operation operation, Operand source)
{
// If the constant has many uses, we also force a new constant mov to be added, in order
// to avoid overflow of the counts field (that is limited to 16 bits).
if (!constants.TryGetValue(source.Value, out var constant) || constant.UsesCount > MaxConstantUses)
if (!constants.TryGetValue(source.Value, out Operand constant) || constant.UsesCount > MaxConstantUses)
{
constant = Local(source.Type);
+1 -1
View File
@@ -123,7 +123,7 @@ namespace ARMeilleure.CodeGen.Arm64
public void Cset(Operand rd, ArmCondition condition)
{
var zr = Factory.Register(ZrRegister, RegisterType.Integer, rd.Type);
Operand zr = Factory.Register(ZrRegister, RegisterType.Integer, rd.Type);
Csinc(rd, zr, zr, (ArmCondition)((int)condition ^ 1));
}
@@ -91,7 +91,7 @@ namespace ARMeilleure.CodeGen.Arm64
long target = _stream.Position;
if (_pendingBranches.TryGetValue(block, out var list))
if (_pendingBranches.TryGetValue(block, out List<(ArmCondition Condition, long BranchPos)> list))
{
foreach ((ArmCondition condition, long branchPos) in list)
{
@@ -119,7 +119,7 @@ namespace ARMeilleure.CodeGen.Arm64
}
else
{
if (!_pendingBranches.TryGetValue(target, out var list))
if (!_pendingBranches.TryGetValue(target, out List<(ArmCondition Condition, long BranchPos)> list))
{
list = new List<(ArmCondition, long)>();
_pendingBranches.Add(target, list);
@@ -321,7 +321,7 @@ namespace ARMeilleure.CodeGen.Arm64
Debug.Assert(comp.Kind == OperandKind.Constant);
var cond = ((Comparison)comp.AsInt32()).ToArmCondition();
ArmCondition cond = ((Comparison)comp.AsInt32()).ToArmCondition();
GenerateCompareCommon(context, operation);
@@ -353,7 +353,7 @@ namespace ARMeilleure.CodeGen.Arm64
Debug.Assert(dest.Type == OperandType.I32);
Debug.Assert(comp.Kind == OperandKind.Constant);
var cond = ((Comparison)comp.AsInt32()).ToArmCondition();
ArmCondition cond = ((Comparison)comp.AsInt32()).ToArmCondition();
GenerateCompareCommon(context, operation);
@@ -847,7 +847,7 @@ namespace ARMeilleure.CodeGen.Arm64
Debug.Assert(comp.Kind == OperandKind.Constant);
var compType = (Comparison)comp.AsInt32();
Comparison compType = (Comparison)comp.AsInt32();
return compType is Comparison.Equal or Comparison.NotEqual;
}
+7 -5
View File
@@ -8,7 +8,7 @@ namespace ARMeilleure.CodeGen
/// <summary>
/// Represents a compiled function.
/// </summary>
readonly struct CompiledFunction
public readonly struct CompiledFunction
{
/// <summary>
/// Gets the machine code of the <see cref="CompiledFunction"/>.
@@ -44,10 +44,11 @@ namespace ARMeilleure.CodeGen
/// <typeparamref name="T"/> pointing to the mapped function.
/// </summary>
/// <typeparam name="T">Type of delegate</typeparam>
/// <param name="jitCache">The jit cache to map the function into</param>
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
public T Map<T>()
public T Map<T>(JitCache jitCache)
{
return MapWithPointer<T>(out _);
return MapWithPointer<T>(jitCache, out _);
}
/// <summary>
@@ -55,11 +56,12 @@ namespace ARMeilleure.CodeGen
/// <typeparamref name="T"/> pointing to the mapped function.
/// </summary>
/// <typeparam name="T">Type of delegate</typeparam>
/// <param name="jitCache">The jit cache to map the function into</param>
/// <param name="codePointer">Pointer to the function code in memory</param>
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
public T MapWithPointer<T>(out nint codePointer)
public T MapWithPointer<T>(JitCache jitCache, out nint codePointer)
{
codePointer = JitCache.Map(this);
codePointer = jitCache.Map(this);
return Marshal.GetDelegateForFunctionPointer<T>(codePointer);
}
@@ -3,7 +3,7 @@ namespace ARMeilleure.CodeGen.Linking
/// <summary>
/// Represents a relocation.
/// </summary>
readonly struct RelocEntry
public readonly struct RelocEntry
{
public const int Stride = 13; // Bytes.
+1 -1
View File
@@ -5,7 +5,7 @@ namespace ARMeilleure.CodeGen.Linking
/// <summary>
/// Represents relocation information about a <see cref="CompiledFunction"/>.
/// </summary>
readonly struct RelocInfo
public readonly struct RelocInfo
{
/// <summary>
/// Gets an empty <see cref="RelocInfo"/>.
+1 -1
View File
@@ -5,7 +5,7 @@ namespace ARMeilleure.CodeGen.Linking
/// <summary>
/// Represents a symbol.
/// </summary>
readonly struct Symbol
public readonly struct Symbol
{
private readonly ulong _value;
@@ -3,7 +3,7 @@ namespace ARMeilleure.CodeGen.Linking
/// <summary>
/// Types of <see cref="Symbol"/>.
/// </summary>
enum SymbolType : byte
public enum SymbolType : byte
{
/// <summary>
/// Refers to nothing, i.e no symbol.
@@ -115,7 +115,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{
NumberLocals(cfg, regMasks.RegistersCount);
var context = new AllocationContext(stackAlloc, regMasks, _intervals.Count);
AllocationContext context = new(stackAlloc, regMasks, _intervals.Count);
BuildIntervals(cfg, context);
@@ -15,12 +15,12 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{
if (_count + 1 > _capacity)
{
var oldSpan = Span;
Span<LiveInterval> oldSpan = Span;
_capacity = Math.Max(4, _capacity * 2);
_items = Allocators.References.Allocate<LiveInterval>((uint)_capacity);
var newSpan = Span;
Span<LiveInterval> newSpan = Span;
oldSpan.CopyTo(newSpan);
}
@@ -16,12 +16,12 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{
if (Count + 1 > _capacity)
{
var oldSpan = Span;
Span<int> oldSpan = Span;
_capacity = Math.Max(4, _capacity * 2);
_items = Allocators.Default.Allocate<int>((uint)_capacity);
var newSpan = Span;
Span<int> newSpan = Span;
oldSpan.CopyTo(newSpan);
}
@@ -1,6 +1,6 @@
namespace ARMeilleure.CodeGen.Unwinding
{
struct UnwindInfo
public struct UnwindInfo
{
public const int Stride = 4; // Bytes.
@@ -1,6 +1,6 @@
namespace ARMeilleure.CodeGen.Unwinding
{
enum UnwindPseudoOp
public enum UnwindPseudoOp
{
PushReg = 0,
SetFrame = 1,
@@ -1,6 +1,6 @@
namespace ARMeilleure.CodeGen.Unwinding
{
struct UnwindPushEntry
public struct UnwindPushEntry
{
public const int Stride = 16; // Bytes.
+8 -7
View File
@@ -1,5 +1,6 @@
using ARMeilleure.CodeGen.Linking;
using ARMeilleure.IntermediateRepresentation;
using Microsoft.IO;
using Ryujinx.Common.Memory;
using System;
using System.Collections.Generic;
@@ -1324,8 +1325,8 @@ namespace ARMeilleure.CodeGen.X86
public (byte[], RelocInfo) GetCode()
{
var jumps = CollectionsMarshal.AsSpan(_jumps);
var relocs = CollectionsMarshal.AsSpan(_relocs);
Span<Jump> jumps = CollectionsMarshal.AsSpan(_jumps);
Span<Reloc> relocs = CollectionsMarshal.AsSpan(_relocs);
// Write jump relative offsets.
bool modified;
@@ -1410,13 +1411,13 @@ namespace ARMeilleure.CodeGen.X86
// Write the code, ignoring the dummy bytes after jumps, into a new stream.
_stream.Seek(0, SeekOrigin.Begin);
using var codeStream = MemoryStreamManager.Shared.GetStream();
var assembler = new Assembler(codeStream, HasRelocs);
using RecyclableMemoryStream codeStream = MemoryStreamManager.Shared.GetStream();
Assembler assembler = new(codeStream, HasRelocs);
bool hasRelocs = HasRelocs;
int relocIndex = 0;
int relocOffset = 0;
var relocEntries = hasRelocs
RelocEntry[] relocEntries = hasRelocs
? new RelocEntry[relocs.Length]
: Array.Empty<RelocEntry>();
@@ -1469,8 +1470,8 @@ namespace ARMeilleure.CodeGen.X86
_stream.CopyTo(codeStream);
var code = codeStream.ToArray();
var relocInfo = new RelocInfo(relocEntries);
byte[] code = codeStream.ToArray();
RelocInfo relocInfo = new(relocEntries);
return (code, relocInfo);
}
+2 -2
View File
@@ -622,7 +622,7 @@ namespace ARMeilleure.CodeGen.X86
Debug.Assert(comp.Kind == OperandKind.Constant);
var cond = ((Comparison)comp.AsInt32()).ToX86Condition();
X86Condition cond = ((Comparison)comp.AsInt32()).ToX86Condition();
GenerateCompareCommon(context, operation);
@@ -660,7 +660,7 @@ namespace ARMeilleure.CodeGen.X86
Debug.Assert(dest.Type == OperandType.I32);
Debug.Assert(comp.Kind == OperandKind.Constant);
var cond = ((Comparison)comp.AsInt32()).ToX86Condition();
X86Condition cond = ((Comparison)comp.AsInt32()).ToX86Condition();
GenerateCompareCommon(context, operation);
@@ -53,7 +53,7 @@ namespace ARMeilleure.CodeGen.X86
memGetXcr0.Reprotect(0, (ulong)asmGetXcr0.Length, MemoryPermission.ReadAndExecute);
var fGetXcr0 = Marshal.GetDelegateForFunctionPointer<GetXcr0>(memGetXcr0.Pointer);
GetXcr0 fGetXcr0 = Marshal.GetDelegateForFunctionPointer<GetXcr0>(memGetXcr0.Pointer);
return fGetXcr0();
}
+1 -1
View File
@@ -759,7 +759,7 @@ namespace ARMeilleure.CodeGen.X86
Debug.Assert(comp.Kind == OperandKind.Constant);
var compType = (Comparison)comp.AsInt32();
Comparison compType = (Comparison)comp.AsInt32();
return compType is Comparison.Equal or Comparison.NotEqual;
}
+2 -2
View File
@@ -13,13 +13,13 @@ namespace ARMeilleure.CodeGen.X86
public static void RunPass(ControlFlowGraph cfg)
{
var constants = new Dictionary<ulong, Operand>();
Dictionary<ulong, Operand> constants = new();
Operand GetConstantCopy(BasicBlock block, Operation operation, Operand source)
{
// If the constant has many uses, we also force a new constant mov to be added, in order
// to avoid overflow of the counts field (that is limited to 16 bits).
if (!constants.TryGetValue(source.Value, out var constant) || constant.UsesCount > MaxConstantUses)
if (!constants.TryGetValue(source.Value, out Operand constant) || constant.UsesCount > MaxConstantUses)
{
constant = Local(source.Type);
+8 -32
View File
@@ -20,45 +20,21 @@ namespace ARMeilleure.Common
new( 1, 6)
];
private static readonly AddressTableLevel[] _levels64BitSparseTiny =
private static readonly AddressTableLevel[] _levels64BitMono =
[
new( 11, 28),
new( 2, 9)
new( 2, 37)
];
private static readonly AddressTableLevel[] _levels32BitSparseTiny =
private static readonly AddressTableLevel[] _levels32BitMono =
[
new( 10, 22),
new( 1, 9)
new( 1, 31)
];
private static readonly AddressTableLevel[] _levels64BitSparseGiant =
[
new( 38, 1),
new( 2, 36)
];
private static readonly AddressTableLevel[] _levels32BitSparseGiant =
[
new( 31, 1),
new( 1, 30)
];
//high power will run worse on DDR3 systems and some DDR4 systems due to the higher ram utilization
//low power will never run worse than non-sparse, but for most systems it won't be necessary
//high power is always used, but I've left low power in here for future reference
public static AddressTableLevel[] GetArmPreset(bool for64Bits, bool sparse, bool lowPower = false)
public static AddressTableLevel[] GetArmPreset(bool for64Bits, bool mono)
{
if (sparse)
if (mono)
{
if (lowPower)
{
return for64Bits ? _levels64BitSparseTiny : _levels32BitSparseTiny;
}
else
{
return for64Bits ? _levels64BitSparseGiant : _levels32BitSparseGiant;
}
return for64Bits ? _levels64BitMono : _levels32BitMono;
}
else
{
@@ -0,0 +1,8 @@
namespace ARMeilleure.Common
{
public enum AddressTableType
{
Default,
Sparse
}
}
+3 -3
View File
@@ -129,13 +129,13 @@ namespace ARMeilleure.Common
if (count > _count)
{
var oldMask = _masks;
var oldSpan = new Span<long>(_masks, _count);
long* oldMask = _masks;
Span<long> oldSpan = new(_masks, _count);
_masks = _allocator.Allocate<long>((uint)count);
_count = count;
var newSpan = new Span<long>(_masks, _count);
Span<long> newSpan = new(_masks, _count);
oldSpan.CopyTo(newSpan);
newSpan[oldSpan.Length..].Clear();
+9
View File
@@ -53,5 +53,14 @@ namespace ARMeilleure.Common
{
return (bits >> shift) | (bits << (size - shift));
}
public static T AlignUp<T>(T value, T size) where T : IBinaryInteger<T>
=> (value + (size - T.One)) & -size;
public static T AlignDown<T>(T value, T size) where T : IBinaryInteger<T>
=> value & -size;
public static T DivRoundUp<T>(T value, T dividend) where T : IBinaryInteger<T>
=> (value + (dividend - T.One)) / dividend;
}
}
+4 -4
View File
@@ -63,7 +63,7 @@ namespace ARMeilleure.Common
}
int index = _freeHint++;
var page = GetPage(index);
Span<TEntry> page = GetPage(index);
_allocated.Set(index);
@@ -111,7 +111,7 @@ namespace ARMeilleure.Common
throw new ArgumentException("Entry at the specified index was not allocated", nameof(index));
}
var page = GetPage(index);
Span<TEntry> page = GetPage(index);
return ref GetValue(page, index);
}
@@ -136,7 +136,7 @@ namespace ARMeilleure.Common
/// <returns>Page for the specified <see cref="index"/></returns>
private unsafe Span<TEntry> GetPage(int index)
{
var pageIndex = (int)((uint)(index & ~(_pageCapacity - 1)) >> _pageLogCapacity);
int pageIndex = (int)((uint)(index & ~(_pageCapacity - 1)) >> _pageLogCapacity);
if (!_pages.TryGetValue(pageIndex, out nint page))
{
@@ -168,7 +168,7 @@ namespace ARMeilleure.Common
{
_allocated.Dispose();
foreach (var page in _pages.Values)
foreach (IntPtr page in _pages.Values)
{
NativeAllocator.Instance.Free((void*)page);
}
+10 -4
View File
@@ -5,11 +5,10 @@ namespace ARMeilleure.Common
public interface IAddressTable<TEntry> : IDisposable where TEntry : unmanaged
{
/// <summary>
/// True if the address table's bottom level is sparsely mapped.
/// This also ensures the second bottom level is filled with a dummy page rather than 0.
/// Gets the <see cref="AddressTableType"/> of the <see cref="IAddressTable{TEntry}"/> instance.
/// </summary>
bool Sparse { get; }
AddressTableType TableType { get; }
/// <summary>
/// Gets the bits used by the <see cref="Levels"/> of the <see cref="IAddressTable{TEntry}"/> instance.
/// </summary>
@@ -31,6 +30,13 @@ namespace ARMeilleure.Common
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
nint Base { get; }
/// <summary>
/// Signal that the given code range exists.
/// </summary>
/// <param name="address">Guest code range address</param>
/// <param name="size">Guest code range size</param>
void SignalCodeRange(ulong address, ulong size);
/// <summary>
/// Determines if the specified <paramref name="address"/> is in the range of the
/// <see cref="IAddressTable{TEntry}"/>.
@@ -9,7 +9,7 @@ namespace ARMeilleure.Decoders
public OpCode32SimdDupElem(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb)
{
var opc = (opCode >> 16) & 0xf;
int opc = (opCode >> 16) & 0xf;
if ((opc & 0b1) == 1)
{
@@ -21,7 +21,7 @@ namespace ARMeilleure.Decoders
Op = (opCode >> 20) & 0x1;
U = ((opCode >> 23) & 1) != 0;
var opc = (((opCode >> 23) & 1) << 4) | (((opCode >> 21) & 0x3) << 2) | ((opCode >> 5) & 0x3);
int opc = (((opCode >> 23) & 1) << 4) | (((opCode >> 21) & 0x3) << 2) | ((opCode >> 5) & 0x3);
if ((opc & 0b01000) == 0b01000)
{
+1 -1
View File
@@ -20,7 +20,7 @@ namespace ARMeilleure.Decoders
}
else if (DataOp == DataOp.Logical)
{
var bm = DecoderHelper.DecodeBitMask(opCode, true);
DecoderHelper.BitMask bm = DecoderHelper.DecodeBitMask(opCode, true);
if (bm.IsUndefined)
{
+1 -1
View File
@@ -11,7 +11,7 @@ namespace ARMeilleure.Decoders
public OpCodeBfm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
var bm = DecoderHelper.DecodeBitMask(opCode, false);
DecoderHelper.BitMask bm = DecoderHelper.DecodeBitMask(opCode, false);
if (bm.IsUndefined)
{
@@ -69,7 +69,7 @@ namespace ARMeilleure.Decoders.Optimizations
}
}
var newBlocks = new List<Block>(blocks.Count);
List<Block> newBlocks = new(blocks.Count);
// Finally, rebuild decoded block list, ignoring blocks outside the contiguous range.
for (int i = 0; i < blocks.Count; i++)
+2 -2
View File
@@ -141,7 +141,7 @@ namespace ARMeilleure.Diagnostics
break;
case OperandKind.Memory:
var memOp = operand.GetMemory();
MemoryOperand memOp = operand.GetMemory();
_builder.Append('[');
@@ -284,7 +284,7 @@ namespace ARMeilleure.Diagnostics
public static string GetDump(ControlFlowGraph cfg)
{
var dumper = new IRDumper(1);
IRDumper dumper = new(1);
for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext)
{
@@ -415,7 +415,7 @@ namespace ARMeilleure.Instructions
{
IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp;
var msb = op.Lsb + op.Msb; // For this instruction, the msb is actually a width.
int msb = op.Lsb + op.Msb; // For this instruction, the msb is actually a width.
Operand n = GetIntA32(context, op.Rn);
Operand res = context.ShiftRightSI(context.ShiftLeft(n, Const(31 - msb)), Const(31 - op.Msb));
@@ -547,7 +547,7 @@ namespace ARMeilleure.Instructions
{
IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp;
var msb = op.Lsb + op.Msb; // For this instruction, the msb is actually a width.
int msb = op.Lsb + op.Msb; // For this instruction, the msb is actually a width.
Operand n = GetIntA32(context, op.Rn);
Operand res = context.ShiftRightUI(context.ShiftLeft(n, Const(31 - msb)), Const(31 - op.Msb));
@@ -1,10 +1,11 @@
using ARMeilleure.CodeGen.Linking;
using ARMeilleure.Common;
using ARMeilleure.Decoders;
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State;
using ARMeilleure.Translation;
using ARMeilleure.Translation.PTC;
using System.Linq;
using static ARMeilleure.Instructions.InstEmitHelper;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
@@ -223,7 +224,7 @@ namespace ARMeilleure.Instructions
Operand hostAddress;
var table = context.FunctionTable;
IAddressTable<ulong> table = context.FunctionTable;
// If address is mapped onto the function table, we can skip the table walk. Otherwise we fallback
// onto the dispatch stub.
@@ -235,9 +236,9 @@ namespace ARMeilleure.Instructions
hostAddress = context.Load(OperandType.I64, hostAddressAddr);
}
else if (table.Sparse)
else if (table.TableType == AddressTableType.Sparse)
{
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
// Inline table lookup. Only enabled when the sparse function table is enabled with 1 level.
// Deliberately attempts to avoid branches.
Operand tableBase = !context.HasPtc ?
@@ -246,24 +247,21 @@ namespace ARMeilleure.Instructions
hostAddress = tableBase;
for (int i = 0; i < table.Levels.Length; i++)
{
var level = table.Levels[i];
int clearBits = 64 - (level.Index + level.Length);
AddressTableLevel level = table.Levels.Last();
int clearBits = 64 - (level.Index + level.Length);
Operand index = context.ShiftLeft(
context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits)), Const(clearBits + level.Index)),
Const(3)
);
Operand index = context.ShiftLeft(
context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits)), Const(clearBits + level.Index)),
Const(3)
);
hostAddress = context.Load(OperandType.I64, context.Add(hostAddress, index));
}
hostAddress = context.Load(OperandType.I64, context.Add(hostAddress, index));
}
else
{
hostAddress = !context.HasPtc ?
Const((long)context.Stubs.DispatchStub) :
Const((long)context.Stubs.DispatchStub, Ptc.DispatchStubSymbol);
Const(context.Stubs.DispatchStub) :
Const(context.Stubs.DispatchStub, Ptc.DispatchStubSymbol);
}
if (isJump)
@@ -143,8 +143,8 @@ namespace ARMeilleure.Instructions
Operand address = context.Copy(GetIntA32(context, op.Rn));
var exclusive = (accType & AccessType.Exclusive) != 0;
var ordered = (accType & AccessType.Ordered) != 0;
bool exclusive = (accType & AccessType.Exclusive) != 0;
bool ordered = (accType & AccessType.Ordered) != 0;
if ((accType & AccessType.Load) != 0)
{
@@ -229,7 +229,7 @@ namespace ARMeilleure.Instructions
private static Operand ZerosOrOnes(ArmEmitterContext context, Operand fromBool, OperandType baseType)
{
var ones = (baseType == OperandType.I64) ? Const(-1L) : Const(-1);
Operand ones = (baseType == OperandType.I64) ? Const(-1L) : Const(-1);
return context.ConditionalSelect(fromBool, ones, Const(baseType, 0L));
}
@@ -118,15 +118,15 @@ namespace ARMeilleure.Instructions
{
OpCode32SimdCvtFFixed op = (OpCode32SimdCvtFFixed)context.CurrOp;
var toFixed = op.Opc == 1;
bool toFixed = op.Opc == 1;
int fracBits = op.Fbits;
var unsigned = op.U;
bool unsigned = op.U;
if (toFixed) // F32 to S32 or U32 (fixed)
{
EmitVectorUnaryOpF32(context, (op1) =>
{
var scaledValue = context.Multiply(op1, ConstF(MathF.Pow(2f, fracBits)));
Operand scaledValue = context.Multiply(op1, ConstF(MathF.Pow(2f, fracBits)));
MethodInfo info = unsigned ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)) : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32));
return context.Call(info, scaledValue);
@@ -136,7 +136,7 @@ namespace ARMeilleure.Instructions
{
EmitVectorUnaryOpI32(context, (op1) =>
{
var floatValue = unsigned ? context.ConvertToFPUI(OperandType.FP32, op1) : context.ConvertToFP(OperandType.FP32, op1);
Operand floatValue = unsigned ? context.ConvertToFPUI(OperandType.FP32, op1) : context.ConvertToFP(OperandType.FP32, op1);
return context.Multiply(floatValue, ConstF(1f / MathF.Pow(2f, fracBits)));
}, !unsigned);
@@ -87,7 +87,7 @@ namespace ARMeilleure.Instructions
{
if (op.Replicate)
{
var regs = (count > 1) ? 1 : op.Increment;
int regs = (count > 1) ? 1 : op.Increment;
for (int reg = 0; reg < regs; reg++)
{
int dreg = reg + d;
+2 -2
View File
@@ -1538,7 +1538,7 @@ namespace ARMeilleure.Instructions
}
else if (MathF.Abs(value) < MathF.Pow(2f, -128))
{
var overflowToInf = fpcr.GetRoundingMode() switch
bool overflowToInf = fpcr.GetRoundingMode() switch
{
FPRoundingMode.ToNearest => true,
FPRoundingMode.TowardsPlusInfinity => !sign,
@@ -3073,7 +3073,7 @@ namespace ARMeilleure.Instructions
}
else if (Math.Abs(value) < Math.Pow(2d, -1024))
{
var overflowToInf = fpcr.GetRoundingMode() switch
bool overflowToInf = fpcr.GetRoundingMode() switch
{
FPRoundingMode.ToNearest => true,
FPRoundingMode.TowardsPlusInfinity => !sign,
@@ -304,7 +304,7 @@ namespace ARMeilleure.IntermediateRepresentation
ushort newCount = checked((ushort)(count + 1));
ushort newCapacity = (ushort)Math.Min(capacity * 2, ushort.MaxValue);
var oldSpan = new Span<T>(data, count);
Span<T> oldSpan = new(data, count);
capacity = newCapacity;
data = Allocators.References.Allocate<T>(capacity);
@@ -338,7 +338,7 @@ namespace ARMeilleure.IntermediateRepresentation
throw new OverflowException();
}
var oldSpan = new Span<T>(data, (int)count);
Span<T> oldSpan = new(data, (int)count);
capacity = newCapacity;
data = Allocators.References.Allocate<T>(capacity);
@@ -352,7 +352,7 @@ namespace ARMeilleure.IntermediateRepresentation
private static void Remove<T>(in T item, ref T* data, ref ushort count) where T : unmanaged
{
var span = new Span<T>(data, count);
Span<T> span = new(data, count);
for (int i = 0; i < span.Length; i++)
{
@@ -372,7 +372,7 @@ namespace ARMeilleure.IntermediateRepresentation
private static void Remove<T>(in T item, ref T* data, ref uint count) where T : unmanaged
{
var span = new Span<T>(data, (int)count);
Span<T> span = new(data, (int)count);
for (int i = 0; i < span.Length; i++)
{
+1 -1
View File
@@ -2,7 +2,7 @@ using System;
namespace ARMeilleure.Memory
{
public class ReservedRegion
public class ReservedRegion : IDisposable
{
public const int DefaultGranularity = 65536; // Mapping granularity in Windows.
+9 -8
View File
@@ -1,5 +1,6 @@
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Translation;
using ARMeilleure.Translation.Cache;
using System.Runtime.InteropServices;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
@@ -17,11 +18,11 @@ namespace ARMeilleure.Signal
public delegate int DebugThreadLocalMapGetOrReserve(int threadId, int initialState);
public delegate void DebugNativeWriteLoop(nint nativeWriteLoopPtr, nint writePtr);
public static DebugPartialUnmap GenerateDebugPartialUnmap()
public static DebugPartialUnmap GenerateDebugPartialUnmap(JitCache jitCache)
{
EmitterContext context = new();
var result = WindowsPartialUnmapHandler.EmitRetryFromAccessViolation(context);
Operand result = WindowsPartialUnmapHandler.EmitRetryFromAccessViolation(context);
context.Return(result);
@@ -31,14 +32,14 @@ namespace ARMeilleure.Signal
OperandType[] argTypes = [OperandType.I64];
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugPartialUnmap>();
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugPartialUnmap>(jitCache);
}
public static DebugThreadLocalMapGetOrReserve GenerateDebugThreadLocalMapGetOrReserve(nint structPtr)
public static DebugThreadLocalMapGetOrReserve GenerateDebugThreadLocalMapGetOrReserve(JitCache jitCache, nint structPtr)
{
EmitterContext context = new();
var result = WindowsPartialUnmapHandler.EmitThreadLocalMapIntGetOrReserve(context, structPtr, context.LoadArgument(OperandType.I32, 0), context.LoadArgument(OperandType.I32, 1));
Operand result = WindowsPartialUnmapHandler.EmitThreadLocalMapIntGetOrReserve(context, structPtr, context.LoadArgument(OperandType.I32, 0), context.LoadArgument(OperandType.I32, 1));
context.Return(result);
@@ -48,10 +49,10 @@ namespace ARMeilleure.Signal
OperandType[] argTypes = [OperandType.I64];
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugThreadLocalMapGetOrReserve>();
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugThreadLocalMapGetOrReserve>(jitCache);
}
public static DebugNativeWriteLoop GenerateDebugNativeWriteLoop()
public static DebugNativeWriteLoop GenerateDebugNativeWriteLoop(JitCache jitCache)
{
EmitterContext context = new();
@@ -77,7 +78,7 @@ namespace ARMeilleure.Signal
OperandType[] argTypes = [OperandType.I64];
return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugNativeWriteLoop>();
return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugNativeWriteLoop>(jitCache);
}
}
}
@@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
namespace ARMeilleure.Translation.Cache
{
readonly struct CacheEntry : IComparable<CacheEntry>
public readonly struct CacheEntry : IComparable<CacheEntry>
{
public int Offset { get; }
public int Size { get; }
@@ -23,11 +23,30 @@ namespace ARMeilleure.Translation.Cache
}
}
private readonly int _regionSize;
private int _regionCount;
private readonly List<MemoryBlock> _blocks = [];
public CacheMemoryAllocator(int capacity)
public CacheMemoryAllocator(int regionSize, int initialRegionCount = 1)
{
_blocks.Add(new MemoryBlock(0, capacity));
_regionCount = 0;
_regionSize = regionSize;
for (; initialRegionCount > 0; initialRegionCount--)
{
_blocks.Add(new MemoryBlock(_regionSize * _regionCount, _regionSize));
_regionCount++;
}
}
public void AddNewBlocks(int count)
{
for (; count > 0; count--)
{
_blocks.Add(new MemoryBlock(_regionSize * _regionCount, _regionSize));
_regionCount++;
}
}
public int Allocate(int size)
@@ -66,12 +85,13 @@ namespace ARMeilleure.Translation.Cache
index = ~index;
}
if (index < _blocks.Count)
int endOffs = block.Offset + block.Size;
// Don't merge blocks from different allocations
if (index < _blocks.Count && endOffs % _regionSize != 0)
{
MemoryBlock next = _blocks[index];
int endOffs = block.Offset + block.Size;
if (next.Offset == endOffs)
{
block = new MemoryBlock(block.Offset, block.Size + next.Size);
@@ -79,7 +99,8 @@ namespace ARMeilleure.Translation.Cache
}
}
if (index > 0)
// Don't merge blocks from different allocations
if (index > 0 && block.Offset % _regionSize != 0)
{
MemoryBlock prev = _blocks[index - 1];
+85 -93
View File
@@ -14,62 +14,35 @@ using System.Threading;
namespace ARMeilleure.Translation.Cache
{
static partial class JitCache
public partial class JitCache : IDisposable
{
private static readonly int _pageSize = (int)MemoryBlock.GetPageSize();
private static readonly int _pageMask = _pageSize - 1;
private const int CodeAlignment = 4; // Bytes.
private const int CacheSize = 256 * 1024 * 1024;
private const uint CacheSize = 256 * 1024 * 1024;
private static JitCacheInvalidation _jitCacheInvalidator;
private readonly JitCacheInvalidation _jitCacheInvalidator;
private static List<CacheMemoryAllocator> _cacheAllocators = [];
private readonly CacheMemoryAllocator _cacheAllocator;
private static readonly List<CacheEntry> _cacheEntries = [];
private readonly List<CacheEntry> _cacheEntries = [];
private static readonly Lock _lock = new();
private static bool _initialized;
private readonly Lock _lock = new();
private static readonly List<ReservedRegion> _jitRegions = [];
private static int _activeRegionIndex = 0;
private readonly List<ReservedRegion> _jitRegions = [];
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
private static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
public static void Initialize(IJitMemoryAllocator allocator)
public JitCache(IJitMemoryAllocator allocator)
{
lock (_lock)
{
if (_initialized)
{
if (OperatingSystem.IsWindows())
{
JitUnwindWindows.RemoveFunctionTableHandler(
_jitRegions[0].Pointer);
}
_jitRegions.Add(new(allocator, CacheSize));
for (int i = 0; i < _jitRegions.Count; i++)
{
_jitRegions[i].Dispose();
}
_jitRegions.Clear();
_cacheAllocators.Clear();
}
else
{
_initialized = true;
}
_activeRegionIndex = 0;
var firstRegion = new ReservedRegion(allocator, CacheSize);
_jitRegions.Add(firstRegion);
CacheMemoryAllocator firstCacheAllocator = new(CacheSize);
_cacheAllocators.Add(firstCacheAllocator);
_cacheAllocator = new((int)CacheSize);
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{
@@ -79,23 +52,20 @@ namespace ARMeilleure.Translation.Cache
if (OperatingSystem.IsWindows())
{
JitUnwindWindows.InstallFunctionTableHandler(
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
this, _jitRegions[0].Pointer, CacheSize, _jitRegions[0].Pointer + Allocate(_pageSize)
);
}
}
}
public static nint Map(CompiledFunction func)
public nint Map(CompiledFunction func)
{
byte[] code = func.Code;
lock (_lock)
{
Debug.Assert(_initialized);
int funcOffset = Allocate(code.Length);
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
nint funcPtr = targetRegion.Pointer + funcOffset;
nint funcPtr = GetFunctionPtr(funcOffset);
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
@@ -109,9 +79,9 @@ namespace ARMeilleure.Translation.Cache
}
else
{
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
ReprotectAsWritable(funcOffset, code.Length);
Marshal.Copy(code, 0, funcPtr, code.Length);
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
ReprotectAsExecutable(funcOffset, code.Length);
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
@@ -129,25 +99,24 @@ namespace ARMeilleure.Translation.Cache
}
}
public static void Unmap(nint pointer)
public void Unmap(nint pointer)
{
lock (_lock)
{
Debug.Assert(_initialized);
foreach (var region in _jitRegions)
for (int i = 0; i < _jitRegions.Count; i++)
{
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
ReservedRegion jitRegion = _jitRegions[i];
if (pointer.ToInt64() < jitRegion.Pointer.ToInt64() ||
pointer.ToInt64() >= (jitRegion.Pointer + (nint)CacheSize).ToInt64())
{
continue;
}
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
int funcOffset = (int)(pointer.ToInt64() - jitRegion.Pointer.ToInt64() + i * CacheSize);
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{
_cacheAllocators[_activeRegionIndex].Free(funcOffset, AlignCodeSize(entry.Size));
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex);
}
@@ -156,53 +125,66 @@ namespace ARMeilleure.Translation.Cache
}
}
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
private void ReprotectAsWritable(int offset, int size)
{
int endOffs = offset + size;
int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
int regionStart = (offset % (int)CacheSize) & ~_pageMask;
int regionEnd = endOffs % (int)CacheSize == 0
? (((int)CacheSize) + _pageMask) & ~_pageMask
: ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
GetRegion(offset).Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
private void ReprotectAsExecutable(int offset, int size)
{
int endOffs = offset + size;
int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
int regionStart = (offset % (int)CacheSize) & ~_pageMask;
int regionEnd = endOffs % (int)CacheSize == 0
? (((int)CacheSize) + _pageMask) & ~_pageMask
: ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
GetRegion(offset).Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}
private static int Allocate(int codeSize)
private int Allocate(int codeSize)
{
codeSize = AlignCodeSize(codeSize);
int allocOffset = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
int allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset >= 0)
{
_jitRegions[_activeRegionIndex].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
GetRegion(allocOffset).ExpandIfNeeded((ulong)(allocOffset % (int)CacheSize) + (ulong)codeSize);
return allocOffset;
}
int exhaustedRegion = _activeRegionIndex;
var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
_cacheAllocator.AddNewBlocks(1);
ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache of size {(_jitRegions.Count * CacheSize).Bytes()} exhausted, creating new Cache Region ({((_jitRegions.Count + 1) * CacheSize).Bytes()} Total Allocation).");
_jitRegions.Add(newRegion);
_activeRegionIndex = _jitRegions.Count - 1;
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {_activeRegionIndex} ({((long)(_activeRegionIndex + 1) * CacheSize).Bytes()} Total Allocation).");
_cacheAllocators.Add(new CacheMemoryAllocator(CacheSize));
int allocOffsetNew = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
if (allocOffsetNew < 0)
allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset < 0)
{
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
}
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
return allocOffsetNew;
GetRegion(allocOffset).ExpandIfNeeded((ulong)(allocOffset % (int)CacheSize) + (ulong)codeSize);
return allocOffset;
}
private nint GetFunctionPtr(int offset)
{
return GetRegion(offset).Pointer + (offset % (int)CacheSize);
}
private ReservedRegion GetRegion(int offset)
{
int index = offset / (int)CacheSize;
return _jitRegions[index];
}
private static int AlignCodeSize(int codeSize)
@@ -210,7 +192,7 @@ namespace ARMeilleure.Translation.Cache
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
}
private static void Add(int offset, int size, UnwindInfo unwindInfo)
private void Add(int offset, int size, UnwindInfo unwindInfo)
{
CacheEntry entry = new(offset, size, unwindInfo);
@@ -224,25 +206,22 @@ namespace ARMeilleure.Translation.Cache
_cacheEntries.Insert(index, entry);
}
public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
public bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
{
lock (_lock)
{
foreach (var region in _jitRegions)
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
if (index < 0)
{
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
index = ~index - 1;
}
if (index < 0)
{
index = ~index - 1;
}
if (index >= 0)
{
entry = _cacheEntries[index];
entryIndex = index;
return true;
}
if (index >= 0)
{
entry = _cacheEntries[index];
entryIndex = index;
return true;
}
}
@@ -250,5 +229,18 @@ namespace ARMeilleure.Translation.Cache
entryIndex = 0;
return false;
}
public void Dispose()
{
if (OperatingSystem.IsWindows())
{
JitUnwindWindows.RemoveFunctionTableHandler(_jitRegions[0].Pointer);
}
foreach (ReservedRegion jitRegion in _jitRegions)
{
jitRegion.Dispose();
}
}
}
}
@@ -2,6 +2,8 @@
using ARMeilleure.CodeGen.Unwinding;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
@@ -27,6 +29,29 @@ namespace ARMeilleure.Translation.Cache
public unsafe fixed ushort UnwindCodes[MaxUnwindCodesArraySize];
}
private unsafe struct InternalFunctionHandler
{
public InternalFunctionHandler(JitCache jitCache, nint workBufferPtr)
{
_jitCache = jitCache;
_runtimeFunction = (RuntimeFunction*)workBufferPtr;
_unwindInfo = (UnwindInfo*)(workBufferPtr + _sizeOfRuntimeFunction);
}
readonly JitCache _jitCache;
readonly RuntimeFunction* _runtimeFunction;
readonly UnwindInfo* _unwindInfo;
public RuntimeFunction* FunctionTableHandler(ulong controlPc, nint context)
{
return JitUnwindWindows.FunctionTableHandler(_jitCache, _runtimeFunction, _unwindInfo, controlPc, context);
}
}
private enum UnwindOp
{
PushNonvol = 0,
@@ -59,27 +84,28 @@ namespace ARMeilleure.Translation.Cache
private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback;
private static int _sizeOfRuntimeFunction;
private static readonly int _sizeOfRuntimeFunction;
private unsafe static RuntimeFunction* _runtimeFunction;
private static readonly ConcurrentDictionary<ulong, InternalFunctionHandler> _functionTableHandlers = new();
private unsafe static UnwindInfo* _unwindInfo;
static JitUnwindWindows()
{
_sizeOfRuntimeFunction = Marshal.SizeOf<RuntimeFunction>();
}
public static void InstallFunctionTableHandler(nint codeCachePointer, uint codeCacheLength, nint workBufferPtr)
public static void InstallFunctionTableHandler(JitCache jitCache, nint codeCachePointer, uint codeCacheLength, nint workBufferPtr)
{
ulong codeCachePtr = (ulong)codeCachePointer.ToInt64();
_sizeOfRuntimeFunction = Marshal.SizeOf<RuntimeFunction>();
bool result;
InternalFunctionHandler handler;
unsafe
{
_runtimeFunction = (RuntimeFunction*)workBufferPtr;
handler = new InternalFunctionHandler(jitCache, workBufferPtr);
_unwindInfo = (UnwindInfo*)(workBufferPtr + _sizeOfRuntimeFunction);
_getRuntimeFunctionCallback = new GetRuntimeFunctionCallback(FunctionTableHandler);
_getRuntimeFunctionCallback = handler.FunctionTableHandler;
result = RtlInstallFunctionTableCallback(
codeCachePtr | 3,
@@ -94,6 +120,8 @@ namespace ARMeilleure.Translation.Cache
{
throw new InvalidOperationException("Failure installing function table callback.");
}
_functionTableHandlers.TryAdd(codeCachePtr, handler);
}
public static void RemoveFunctionTableHandler(nint codeCachePointer)
@@ -111,24 +139,26 @@ namespace ARMeilleure.Translation.Cache
{
throw new InvalidOperationException("Failure removing function table callback.");
}
_functionTableHandlers.Remove(codeCachePtr, out _);
}
private static unsafe RuntimeFunction* FunctionTableHandler(ulong controlPc, nint context)
private static unsafe RuntimeFunction* FunctionTableHandler(JitCache jitCache, RuntimeFunction* runtimeFunction, UnwindInfo* unwindInfo, ulong controlPc, nint context)
{
int offset = (int)((long)controlPc - context.ToInt64());
if (!JitCache.TryFind(offset, out CacheEntry funcEntry, out _))
if (!jitCache.TryFind(offset, out CacheEntry funcEntry, out _))
{
return null; // Not found.
}
var unwindInfo = funcEntry.UnwindInfo;
CodeGen.Unwinding.UnwindInfo funcUnwindInfo = funcEntry.UnwindInfo;
int codeIndex = 0;
for (int index = unwindInfo.PushEntries.Length - 1; index >= 0; index--)
for (int index = funcUnwindInfo.PushEntries.Length - 1; index >= 0; index--)
{
var entry = unwindInfo.PushEntries[index];
UnwindPushEntry entry = funcUnwindInfo.PushEntries[index];
switch (entry.PseudoOp)
{
@@ -140,14 +170,14 @@ namespace ARMeilleure.Translation.Cache
if (stackOffset <= 0xFFFF0)
{
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128, entry.PrologOffset, entry.RegIndex);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset / 16);
unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128, entry.PrologOffset, entry.RegIndex);
unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset / 16);
}
else
{
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128Far, entry.PrologOffset, entry.RegIndex);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 0);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 16);
unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128Far, entry.PrologOffset, entry.RegIndex);
unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 0);
unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 16);
}
break;
@@ -161,18 +191,18 @@ namespace ARMeilleure.Translation.Cache
if (allocSize <= 128)
{
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocSmall, entry.PrologOffset, (allocSize / 8) - 1);
unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocSmall, entry.PrologOffset, (allocSize / 8) - 1);
}
else if (allocSize <= 0x7FFF8)
{
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 0);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize / 8);
unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 0);
unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize / 8);
}
else
{
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 1);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 0);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 16);
unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 1);
unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 0);
unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 16);
}
break;
@@ -180,7 +210,7 @@ namespace ARMeilleure.Translation.Cache
case UnwindPseudoOp.PushReg:
{
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.PushNonvol, entry.PrologOffset, entry.RegIndex);
unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.PushNonvol, entry.PrologOffset, entry.RegIndex);
break;
}
@@ -192,16 +222,16 @@ namespace ARMeilleure.Translation.Cache
Debug.Assert(codeIndex <= MaxUnwindCodesArraySize);
_unwindInfo->VersionAndFlags = 1; // Flags: The function has no handler.
_unwindInfo->SizeOfProlog = (byte)unwindInfo.PrologSize;
_unwindInfo->CountOfUnwindCodes = (byte)codeIndex;
_unwindInfo->FrameRegister = 0;
unwindInfo->VersionAndFlags = 1; // Flags: The function has no handler.
unwindInfo->SizeOfProlog = (byte)funcUnwindInfo.PrologSize;
unwindInfo->CountOfUnwindCodes = (byte)codeIndex;
unwindInfo->FrameRegister = 0;
_runtimeFunction->BeginAddress = (uint)funcEntry.Offset;
_runtimeFunction->EndAddress = (uint)(funcEntry.Offset + funcEntry.Size);
_runtimeFunction->UnwindData = (uint)_sizeOfRuntimeFunction;
runtimeFunction->BeginAddress = (uint)funcEntry.Offset;
runtimeFunction->EndAddress = (uint)(funcEntry.Offset + funcEntry.Size);
runtimeFunction->UnwindData = (uint)_sizeOfRuntimeFunction;
return _runtimeFunction;
return runtimeFunction;
}
private static ushort PackUnwindOp(UnwindOp op, int prologOffset, int opInfo)
@@ -47,8 +47,8 @@ namespace ARMeilleure.Translation
{
RemoveUnreachableBlocks(Blocks);
var visited = new HashSet<BasicBlock>();
var blockStack = new Stack<BasicBlock>();
HashSet<BasicBlock> visited = new();
Stack<BasicBlock> blockStack = new();
Array.Resize(ref _postOrderBlocks, Blocks.Count);
Array.Resize(ref _postOrderMap, Blocks.Count);
@@ -88,8 +88,8 @@ namespace ARMeilleure.Translation
private void RemoveUnreachableBlocks(IntrusiveList<BasicBlock> blocks)
{
var visited = new HashSet<BasicBlock>();
var workQueue = new Queue<BasicBlock>();
HashSet<BasicBlock> visited = new();
Queue<BasicBlock> workQueue = new();
visited.Add(Entry);
workQueue.Enqueue(Entry);
+46 -32
View File
@@ -4,12 +4,14 @@ using ARMeilleure.CodeGen.Unwinding;
using ARMeilleure.Common;
using ARMeilleure.Memory;
using ARMeilleure.State;
using ARMeilleure.Translation.Cache;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using System;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@@ -30,14 +32,11 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 7010; //! To be incremented manually for each change to the ARMeilleure project.
private const uint InternalVersion = 7031; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";
private const string TitleIdTextDefault = "0000000000000000";
private const string DisplayVersionDefault = "0";
public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1);
public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2);
public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3);
@@ -48,6 +47,8 @@ namespace ARMeilleure.Translation.PTC
public PtcProfiler Profiler { get; }
private readonly JitCache _jitCache;
// Carriers.
private MemoryStream _infosStream;
private List<byte[]> _codesList;
@@ -63,8 +64,7 @@ namespace ARMeilleure.Translation.PTC
private bool _disposed;
public string TitleIdText { get; private set; }
public string DisplayVersion { get; private set; }
public PtcCacheInfo CacheInfo { get; private set; }
private MemoryManagerType _memoryMode;
@@ -78,7 +78,7 @@ namespace ARMeilleure.Translation.PTC
private volatile int _translateTotalCount;
public event Action<PtcLoadingState, int, int> PtcStateChanged;
public Ptc()
public Ptc(JitCache jitCache)
{
Profiler = new PtcProfiler(this);
@@ -89,10 +89,11 @@ namespace ARMeilleure.Translation.PTC
_waitEvent = new ManualResetEvent(true);
_jitCache = jitCache;
_disposed = false;
TitleIdText = TitleIdTextDefault;
DisplayVersion = DisplayVersionDefault;
CacheInfo = new PtcCacheInfo(0, null, null, 0, null, "Unknown", "default");
CachePathActual = string.Empty;
CachePathBackup = string.Empty;
@@ -100,20 +101,24 @@ namespace ARMeilleure.Translation.PTC
Disable();
}
public void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerType memoryMode, string cacheSelector)
public void Initialize(PtcCacheInfo cacheInfo, bool enabled, MemoryManagerType memoryMode)
{
Wait();
Profiler.Wait();
Profiler.ClearEntries();
Logger.Info?.Print(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache v{InternalVersion}\n\t\t (title: {titleIdText}, version: '{displayVersion}', selector: '{cacheSelector}', enabled: {enabled}).");
CacheInfo = cacheInfo;
if (!enabled || string.IsNullOrEmpty(titleIdText) || titleIdText == TitleIdTextDefault)
Logger.Info?.Print(
LogClass.Ptc,
$"Initializing Profiled Persistent Translation Cache v{InternalVersion}\n\t\t " +
$"(pid: {cacheInfo.ProcessId}, title: {cacheInfo.TitleIdText}, application: {cacheInfo.ApplicationIdText}, " +
$"programIndex: {cacheInfo.ProgramIndex}, version: '{cacheInfo.DisplayVersion}', kind: {cacheInfo.ProcessKind}, " +
$"selector: '{cacheInfo.CacheSelector}', key: '{cacheInfo.CacheKey}', enabled: {enabled}).");
if (!enabled || cacheInfo.TitleIdText == PtcCacheInfo.TitleIdTextDefault)
{
TitleIdText = TitleIdTextDefault;
DisplayVersion = DisplayVersionDefault;
CachePathActual = string.Empty;
CachePathBackup = string.Empty;
@@ -122,12 +127,10 @@ namespace ARMeilleure.Translation.PTC
return;
}
TitleIdText = titleIdText;
DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
_memoryMode = memoryMode;
string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir);
string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir);
string workPathActual = Path.Combine(AppDataManager.GamesDirPath, CacheInfo.TitleIdText, "cache", "cpu", ActualDir);
string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, CacheInfo.TitleIdText, "cache", "cpu", BackupDir);
if (!Directory.Exists(workPathActual))
{
@@ -139,8 +142,14 @@ namespace ARMeilleure.Translation.PTC
Directory.CreateDirectory(workPathBackup);
}
CachePathActual = Path.Combine(workPathActual, DisplayVersion) + "-" + cacheSelector;
CachePathBackup = Path.Combine(workPathBackup, DisplayVersion) + "-" + cacheSelector;
CachePathActual = Path.Combine(workPathActual, CacheInfo.DisplayVersion) + "-" + CacheInfo.CacheSelector;
CachePathBackup = Path.Combine(workPathBackup, CacheInfo.DisplayVersion) + "-" + CacheInfo.CacheSelector;
Logger.Info?.Print(
LogClass.Ptc,
$"PPTC cache owner selected (pid: {CacheInfo.ProcessId}, title: {CacheInfo.TitleIdText}, application: {CacheInfo.ApplicationIdText}, " +
$"version: '{CacheInfo.DisplayVersion}', kind: {CacheInfo.ProcessKind}, selector: '{CacheInfo.CacheSelector}', " +
$"key: '{CacheInfo.CacheKey}', path: '{CachePathActual}').");
PreLoad();
Profiler.PreLoad();
@@ -364,7 +373,12 @@ namespace ARMeilleure.Translation.PTC
long fileSize = new FileInfo(fileName).Length;
Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Translation Cache" : "Loaded Translation Cache")} (size: {fileSize} bytes, translated functions: {GetEntriesCount()}).");
Logger.Info?.Print(
LogClass.Ptc,
$"{(isBackup ? "Loaded Backup Translation Cache" : "Loaded Translation Cache")} " +
$"(pid: {CacheInfo.ProcessId}, title: {CacheInfo.TitleIdText}, version: '{CacheInfo.DisplayVersion}', kind: {CacheInfo.ProcessKind}, " +
$"selector: '{CacheInfo.CacheSelector}', key: '{CacheInfo.CacheKey}', path: '{fileName}', " +
$"size: {fileSize} bytes, translated functions: {GetEntriesCount()}).");
return true;
}
@@ -544,7 +558,7 @@ namespace ARMeilleure.Translation.PTC
bool isEntryChanged = infoEntry.Hash != ComputeHash(translator.Memory, infoEntry.Address, infoEntry.GuestSize);
if (isEntryChanged || (!infoEntry.HighCq && Profiler.ProfiledFuncs.TryGetValue(infoEntry.Address, out var value) && value.HighCq))
if (isEntryChanged || (!infoEntry.HighCq && Profiler.ProfiledFuncs.TryGetValue(infoEntry.Address, out PtcProfiler.FuncProfile value) && value.HighCq))
{
infoEntry.Stubbed = true;
infoEntry.CodeLength = 0;
@@ -724,15 +738,15 @@ namespace ARMeilleure.Translation.PTC
return new UnwindInfo(pushEntries, prologueSize);
}
private static TranslatedFunction FastTranslate(
private TranslatedFunction FastTranslate(
byte[] code,
Counter<uint> callCounter,
ulong guestSize,
UnwindInfo unwindInfo,
bool highCq)
{
var cFunc = new CompiledFunction(code, unwindInfo, RelocInfo.Empty);
var gFunc = cFunc.MapWithPointer<GuestFunction>(out nint gFuncPointer);
CompiledFunction cFunc = new(code, unwindInfo, RelocInfo.Empty);
GuestFunction gFunc = cFunc.MapWithPointer<GuestFunction>(_jitCache, out nint gFuncPointer);
return new TranslatedFunction(gFunc, gFuncPointer, callCounter, guestSize, highCq);
}
@@ -769,7 +783,7 @@ namespace ARMeilleure.Translation.PTC
public void MakeAndSaveTranslations(Translator translator)
{
var profiledFuncsToTranslate = Profiler.GetProfiledFuncsToTranslate(translator.Functions);
ConcurrentQueue<(ulong address, PtcProfiler.FuncProfile funcProfile)> profiledFuncsToTranslate = Profiler.GetProfiledFuncsToTranslate(translator.Functions);
_translateCount = 0;
_translateTotalCount = profiledFuncsToTranslate.Count;
@@ -813,7 +827,7 @@ namespace ARMeilleure.Translation.PTC
void TranslateFuncs()
{
while (profiledFuncsToTranslate.TryDequeue(out var item))
while (profiledFuncsToTranslate.TryDequeue(out (ulong address, PtcProfiler.FuncProfile funcProfile) item))
{
ulong address = item.address;
ExecutionMode executionMode = item.funcProfile.Mode;
@@ -858,11 +872,11 @@ namespace ARMeilleure.Translation.PTC
Stopwatch sw = Stopwatch.StartNew();
foreach (var thread in threads)
foreach (Thread thread in threads)
{
thread.Start();
}
foreach (var thread in threads)
foreach (Thread thread in threads)
{
thread.Join();
}
@@ -942,7 +956,7 @@ namespace ARMeilleure.Translation.PTC
WriteCode(code.AsSpan());
// WriteReloc.
using var relocInfoWriter = new BinaryWriter(_relocsStream, EncodingCache.UTF8NoBOM, true);
using BinaryWriter relocInfoWriter = new(_relocsStream, EncodingCache.UTF8NoBOM, true);
foreach (RelocEntry entry in relocInfo.Entries)
{
@@ -952,7 +966,7 @@ namespace ARMeilleure.Translation.PTC
}
// WriteUnwindInfo.
using var unwindInfoWriter = new BinaryWriter(_unwindInfosStream, EncodingCache.UTF8NoBOM, true);
using BinaryWriter unwindInfoWriter = new(_unwindInfosStream, EncodingCache.UTF8NoBOM, true);
unwindInfoWriter.Write(unwindInfo.PushEntries.Length);
@@ -0,0 +1,37 @@
namespace ARMeilleure.Translation.PTC
{
public readonly struct PtcCacheInfo
{
public const string TitleIdTextDefault = "0000000000000000";
public const string ApplicationIdTextDefault = "0000000000000000";
public const string DisplayVersionDefault = "0";
public ulong ProcessId { get; }
public string TitleIdText { get; }
public string ApplicationIdText { get; }
public byte ProgramIndex { get; }
public string DisplayVersion { get; }
public string ProcessKind { get; }
public string CacheSelector { get; }
public string CacheKey => $"{DisplayVersion}-{CacheSelector}";
public PtcCacheInfo(
ulong processId,
string titleIdText,
string applicationIdText,
byte programIndex,
string displayVersion,
string processKind,
string cacheSelector)
{
ProcessId = processId;
TitleIdText = !string.IsNullOrEmpty(titleIdText) ? titleIdText : TitleIdTextDefault;
ApplicationIdText = !string.IsNullOrEmpty(applicationIdText) ? applicationIdText : ApplicationIdTextDefault;
ProgramIndex = programIndex;
DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
ProcessKind = processKind ?? string.Empty;
CacheSelector = string.IsNullOrEmpty(cacheSelector) ? "default" : cacheSelector;
}
}
}
+15 -6
View File
@@ -23,7 +23,7 @@ namespace ARMeilleure.Translation.PTC
{
private const string OuterHeaderMagicString = "Pohd\0\0\0\0";
private const uint InternalVersion = 6698; //! Not to be incremented manually for each change to the ARMeilleure project.
private const uint InternalVersion = 7031; //! Not to be incremented manually for each change to the ARMeilleure project.
private static readonly uint[] _migrateInternalVersions =
[
@@ -119,9 +119,9 @@ namespace ARMeilleure.Translation.PTC
public ConcurrentQueue<(ulong address, FuncProfile funcProfile)> GetProfiledFuncsToTranslate(TranslatorCache<TranslatedFunction> funcs)
{
var profiledFuncsToTranslate = new ConcurrentQueue<(ulong address, FuncProfile funcProfile)>();
ConcurrentQueue<(ulong address, FuncProfile funcProfile)> profiledFuncsToTranslate = new();
foreach (var profiledFunc in ProfiledFuncs)
foreach (KeyValuePair<ulong, FuncProfile> profiledFunc in ProfiledFuncs)
{
if (!funcs.ContainsKey(profiledFunc.Key) && !profiledFunc.Value.Blacklist)
{
@@ -142,7 +142,7 @@ namespace ARMeilleure.Translation.PTC
{
List<ulong> funcs = [];
foreach (var profiledFunc in ProfiledFuncs)
foreach (KeyValuePair<ulong, FuncProfile> profiledFunc in ProfiledFuncs)
{
if (profiledFunc.Value.Blacklist)
{
@@ -254,7 +254,12 @@ namespace ARMeilleure.Translation.PTC
long fileSize = new FileInfo(fileName).Length;
Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Profiling Info" : "Loaded Profiling Info")} (size: {fileSize} bytes, profiled functions: {ProfiledFuncs.Count}).");
Logger.Info?.Print(
LogClass.Ptc,
$"{(isBackup ? "Loaded Backup Profiling Info" : "Loaded Profiling Info")} " +
$"(pid: {_ptc.CacheInfo.ProcessId}, title: {_ptc.CacheInfo.TitleIdText}, version: '{_ptc.CacheInfo.DisplayVersion}', " +
$"kind: {_ptc.CacheInfo.ProcessKind}, selector: '{_ptc.CacheInfo.CacheSelector}', key: '{_ptc.CacheInfo.CacheKey}', " +
$"path: '{fileName}', size: {fileSize} bytes, profiled functions: {ProfiledFuncs.Count}).");
return true;
}
@@ -375,7 +380,11 @@ namespace ARMeilleure.Translation.PTC
if (fileSize != 0L)
{
Logger.Info?.Print(LogClass.Ptc, $"Saved Profiling Info (size: {fileSize} bytes, profiled functions: {profiledFuncsCount}).");
Logger.Info?.Print(
LogClass.Ptc,
$"Saved Profiling Info (pid: {_ptc.CacheInfo.ProcessId}, title: {_ptc.CacheInfo.TitleIdText}, version: '{_ptc.CacheInfo.DisplayVersion}', " +
$"kind: {_ptc.CacheInfo.ProcessKind}, selector: '{_ptc.CacheInfo.CacheSelector}', key: '{_ptc.CacheInfo.CacheKey}', " +
$"path: '{fileName}', size: {fileSize} bytes, profiled functions: {profiledFuncsCount}).");
}
}
@@ -44,10 +44,10 @@ namespace ARMeilleure.Translation
public static void Construct(ControlFlowGraph cfg)
{
var globalDefs = new DefMap[cfg.Blocks.Count];
var localDefs = new Operand[cfg.LocalsCount + RegisterConsts.TotalCount];
DefMap[] globalDefs = new DefMap[cfg.Blocks.Count];
Operand[] localDefs = new Operand[cfg.LocalsCount + RegisterConsts.TotalCount];
var dfPhiBlocks = new Queue<BasicBlock>();
Queue<BasicBlock> dfPhiBlocks = new();
for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext)
{
+23 -14
View File
@@ -24,10 +24,11 @@ namespace ARMeilleure.Translation
private readonly IJitMemoryAllocator _allocator;
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
public readonly JitCache JitCache;
private readonly Ptc _ptc;
internal TranslatorCache<TranslatedFunction> Functions { get; }
internal IAddressTable<ulong> FunctionTable { get; }
public IAddressTable<ulong> FunctionTable { get; }
internal EntryTable<uint> CountTable { get; }
internal TranslatorStubs Stubs { get; }
internal TranslatorQueue Queue { get; }
@@ -43,23 +44,26 @@ namespace ARMeilleure.Translation
_oldFuncs = new ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>>();
_ptc = new Ptc();
JitCache = new JitCache(allocator);
_ptc = new Ptc(JitCache);
Queue = new TranslatorQueue();
JitCache.Initialize(allocator);
CountTable = new EntryTable<uint>();
Functions = new TranslatorCache<TranslatedFunction>();
FunctionTable = functionTable;
Stubs = new TranslatorStubs(FunctionTable);
Stubs = new TranslatorStubs(JitCache, FunctionTable);
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
}
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
public IPtcLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled)
{
_ptc.Initialize(titleIdText, displayVersion, enabled, Memory.Type, cacheSelector);
_ptc.Initialize(cacheInfo, enabled, Memory.Type);
return _ptc;
}
@@ -167,6 +171,7 @@ namespace ARMeilleure.Translation
}
ClearJitCache();
JitCache.Dispose();
Stubs.Dispose();
FunctionTable.Dispose();
@@ -242,7 +247,7 @@ namespace ARMeilleure.Translation
internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false, bool pptcTranslation = false)
{
var context = new ArmEmitterContext(
ArmEmitterContext context = new(
Memory,
CountTable,
FunctionTable,
@@ -286,10 +291,10 @@ namespace ARMeilleure.Translation
Logger.EndPass(PassName.RegisterUsage);
var retType = OperandType.I64;
var argTypes = new OperandType[] { OperandType.I64 };
OperandType retType = OperandType.I64;
OperandType[] argTypes = new OperandType[] { OperandType.I64 };
var options = highCq ? CompilerOptions.HighCq : CompilerOptions.None;
CompilerOptions options = highCq ? CompilerOptions.HighCq : CompilerOptions.None;
if (context.HasPtc && !singleStep)
{
@@ -305,7 +310,7 @@ namespace ARMeilleure.Translation
_ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc);
}
GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(out nint funcPointer);
GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(JitCache, out nint funcPointer);
Allocators.ResetAll();
@@ -408,6 +413,10 @@ namespace ARMeilleure.Translation
context.CurrOp = opCode;
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
Operand dispAddressAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
context.Store(dispAddressAddr, Const(opCode.Address));
bool isLastOp = opcIndex == block.OpCodes.Count - 1;
if (isLastOp)
@@ -574,7 +583,7 @@ namespace ARMeilleure.Translation
List<TranslatedFunction> functions = Functions.AsList();
foreach (var func in functions)
foreach (TranslatedFunction func in functions)
{
JitCache.Unmap(func.FuncPointer);
@@ -583,7 +592,7 @@ namespace ARMeilleure.Translation
Functions.Clear();
while (_oldFuncs.TryDequeue(out var kv))
while (_oldFuncs.TryDequeue(out KeyValuePair<ulong, TranslatedFunction> kv))
{
JitCache.Unmap(kv.Value.FuncPointer);
@@ -604,7 +613,7 @@ namespace ARMeilleure.Translation
{
while (Queue.Count > 0 && Queue.TryDequeue(out RejitRequest request))
{
if (Functions.TryGetValue(request.Address, out var func) && func.CallCounter != null)
if (Functions.TryGetValue(request.Address, out TranslatedFunction func) && func.CallCounter != null)
{
Volatile.Write(ref func.CallCounter.Value, 0);
}
+29 -24
View File
@@ -14,6 +14,8 @@ namespace ARMeilleure.Translation
/// </summary>
class TranslatorStubs : IDisposable
{
private readonly JitCache _jitCache;
private readonly Lazy<nint> _slowDispatchStub;
private bool _disposed;
@@ -83,12 +85,15 @@ namespace ARMeilleure.Translation
/// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
/// <see cref="Translator"/> instance.
/// </summary>
/// <param name="jitCache">Jit cache to map functions in</param>
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
public TranslatorStubs(IAddressTable<ulong> functionTable)
public TranslatorStubs(JitCache jitCache, IAddressTable<ulong> functionTable)
{
ArgumentNullException.ThrowIfNull(functionTable);
_jitCache = jitCache;
_functionTable = functionTable;
_slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true);
_dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
@@ -115,12 +120,12 @@ namespace ARMeilleure.Translation
{
if (_dispatchStub.IsValueCreated)
{
JitCache.Unmap(_dispatchStub.Value);
_jitCache.Unmap(_dispatchStub.Value);
}
if (_dispatchLoop.IsValueCreated)
{
JitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value));
_jitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value));
}
_disposed = true;
@@ -141,7 +146,7 @@ namespace ARMeilleure.Translation
/// <returns>Generated <see cref="DispatchStub"/></returns>
private nint GenerateDispatchStub()
{
var context = new EmitterContext();
EmitterContext context = new();
Operand lblFallback = Label();
Operand lblEnd = Label();
@@ -160,7 +165,7 @@ namespace ARMeilleure.Translation
for (int i = 0; i < _functionTable.Levels.Length; i++)
{
ref var level = ref _functionTable.Levels[i];
ref AddressTableLevel level = ref _functionTable.Levels[i];
// level.Mask is not used directly because it is more often bigger than 32-bits, so it will not
// be encoded as an immediate on x86's bitwise and operation.
@@ -184,11 +189,11 @@ namespace ARMeilleure.Translation
hostAddress = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), guestAddress);
context.Tailcall(hostAddress, nativeContext);
var cfg = context.GetControlFlowGraph();
var retType = OperandType.I64;
var argTypes = new[] { OperandType.I64 };
ControlFlowGraph cfg = context.GetControlFlowGraph();
OperandType retType = OperandType.I64;
OperandType[] argTypes = new[] { OperandType.I64 };
var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>();
GuestFunction func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>(_jitCache);
return Marshal.GetFunctionPointerForDelegate(func);
}
@@ -199,7 +204,7 @@ namespace ARMeilleure.Translation
/// <returns>Generated <see cref="SlowDispatchStub"/></returns>
private nint GenerateSlowDispatchStub()
{
var context = new EmitterContext();
EmitterContext context = new();
// Load the target guest address from the native context.
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
@@ -209,11 +214,11 @@ namespace ARMeilleure.Translation
Operand hostAddress = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), guestAddress);
context.Tailcall(hostAddress, nativeContext);
var cfg = context.GetControlFlowGraph();
var retType = OperandType.I64;
var argTypes = new[] { OperandType.I64 };
ControlFlowGraph cfg = context.GetControlFlowGraph();
OperandType retType = OperandType.I64;
OperandType[] argTypes = new[] { OperandType.I64 };
var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>();
GuestFunction func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>(_jitCache);
return Marshal.GetFunctionPointerForDelegate(func);
}
@@ -250,7 +255,7 @@ namespace ARMeilleure.Translation
/// <returns><see cref="DispatchLoop"/> function</returns>
private DispatcherFunction GenerateDispatchLoop()
{
var context = new EmitterContext();
EmitterContext context = new();
Operand beginLbl = Label();
Operand endLbl = Label();
@@ -286,11 +291,11 @@ namespace ARMeilleure.Translation
context.Return();
var cfg = context.GetControlFlowGraph();
var retType = OperandType.None;
var argTypes = new[] { OperandType.I64, OperandType.I64 };
ControlFlowGraph cfg = context.GetControlFlowGraph();
OperandType retType = OperandType.None;
OperandType[] argTypes = new[] { OperandType.I64, OperandType.I64 };
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DispatcherFunction>();
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DispatcherFunction>(_jitCache);
}
/// <summary>
@@ -299,7 +304,7 @@ namespace ARMeilleure.Translation
/// <returns><see cref="ContextWrapper"/> function</returns>
private WrapperFunction GenerateContextWrapper()
{
var context = new EmitterContext();
EmitterContext context = new();
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
Operand guestMethod = context.LoadArgument(OperandType.I64, 1);
@@ -310,11 +315,11 @@ namespace ARMeilleure.Translation
context.Return(returnValue);
var cfg = context.GetControlFlowGraph();
var retType = OperandType.I64;
var argTypes = new[] { OperandType.I64, OperandType.I64 };
ControlFlowGraph cfg = context.GetControlFlowGraph();
OperandType retType = OperandType.I64;
OperandType[] argTypes = new[] { OperandType.I64, OperandType.I64 };
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<WrapperFunction>();
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<WrapperFunction>(_jitCache);
}
}
}
@@ -1,6 +1,7 @@
using ARMeilleure.CodeGen.X86;
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State;
using ARMeilleure.Translation.Cache;
using System;
using System.Runtime.InteropServices;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
@@ -59,7 +60,7 @@ namespace ARMeilleure.Translation
return context.VectorExtract(OperandType.I32, vec, 0);
}
public static FpFlagsPInvokeTest GenerateFpFlagsPInvokeTest()
public static FpFlagsPInvokeTest GenerateFpFlagsPInvokeTest(JitCache jitCache)
{
EmitterContext context = new();
@@ -141,7 +142,7 @@ namespace ARMeilleure.Translation
OperandType[] argTypes = [OperandType.I64];
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<FpFlagsPInvokeTest>();
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<FpFlagsPInvokeTest>(jitCache);
}
}
}
@@ -51,7 +51,7 @@ namespace Ryujinx.Audio.Backends.Apple
if (result == 0)
{
AudioChannelLayout layout = new AudioChannelLayout
AudioChannelLayout layout = new()
{
AudioChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_1_A,
AudioChannelBitmap = 0,
@@ -130,7 +130,7 @@ namespace Ryujinx.Audio.Backends.SDL3
{
SDL_AudioSpec desired = GetSDL3Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount);
SDL_AudioSpec got = desired;
var pCallback = callback != null ? (SDL_AudioStreamCallbackPointer)Marshal.GetFunctionPointerForDelegate(callback) : null;
SDL_AudioStreamCallbackPointer pCallback = callback != null ? (SDL_AudioStreamCallbackPointer)Marshal.GetFunctionPointerForDelegate(callback) : null;
// From SDL 3 and on, SDL requires us to set this as a hint
SDL_SetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES, $"{sampleCount}");
@@ -38,7 +38,7 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
get => Marshal.PtrToStringAnsi(GetOutContext().Name);
set
{
var context = GetOutContext();
SoundIoOutStream context = GetOutContext();
if (_nameStored != nint.Zero && context.Name == _nameStored)
{
@@ -129,8 +129,8 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
unsafe
{
var frameCountPtr = &nativeFrameCount;
var arenasPtr = &arenas;
int* frameCountPtr = &nativeFrameCount;
IntPtr* arenasPtr = &arenas;
CheckError(soundio_outstream_begin_write(_context, (nint)arenasPtr, (nint)frameCountPtr));
frameCount = *frameCountPtr;
@@ -27,7 +27,7 @@ namespace Ryujinx.Audio.Renderer.Utils
private void UpdateHeader()
{
var writer = new BinaryWriter(_stream);
BinaryWriter writer = new(_stream);
long currentPos = writer.Seek(0, SeekOrigin.Current);
+1 -1
View File
@@ -34,7 +34,7 @@ namespace Ryujinx.Common
{
try
{
foreach (var item in _queue.GetConsumingEnumerable(_cts.Token))
foreach (T item in _queue.GetConsumingEnumerable(_cts.Token))
{
_workerAction(item);
}
@@ -16,5 +16,10 @@ namespace Ryujinx.Common.Configuration.Hid.Controller
/// Enable Rumble
/// </summary>
public bool EnableRumble { get; set; }
/// <summary>
/// Enable HD Rumble support
/// </summary
public bool UseHDRumble { get; set; }
}
}
@@ -1,6 +1,7 @@
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -16,15 +17,15 @@ namespace Ryujinx.Common.Extensions
/// <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;
long initialConsumed = reader.Consumed;
reader.Rewind(initialConsumed);
using (var fileStream = System.IO.File.Create(fileFullName, 4096, System.IO.FileOptions.None))
using (FileStream fileStream = System.IO.File.Create(fileFullName, 4096, System.IO.FileOptions.None))
{
while (reader.End == false)
{
var span = reader.CurrentSpan;
ReadOnlySpan<byte> span = reader.CurrentSpan;
fileStream.Write(span);
reader.Advance(span.Length);
}
@@ -41,7 +41,7 @@ namespace Ryujinx.Common.Logging.Formatters
sb.Append('{');
foreach (var prop in props)
foreach (PropertyInfo prop in props)
{
sb.Append(prop.Name);
sb.Append(": ");
@@ -52,7 +52,7 @@ namespace Ryujinx.Common.Logging.Formatters
if (array is not null)
{
foreach (var item in array)
foreach (object? item in array)
{
sb.Append(item);
sb.Append(", ");
+19 -8
View File
@@ -136,11 +136,7 @@ namespace Ryujinx.Common.Logging
_time = Stopwatch.StartNew();
// Logger should log to console by default
AddTarget(new AsyncLogTargetWrapper(
new ConsoleLogTarget("console"),
1000,
AsyncLogTargetOverflowAction.Discard));
SetConsoleTargetEnabled(true);
Notice = new Log(LogLevel.Notice);
@@ -173,6 +169,21 @@ namespace Ryujinx.Common.Logging
Updated += target.Log;
}
public static void SetConsoleTargetEnabled(bool enabled)
{
if (enabled)
{
AddTarget(new AsyncLogTargetWrapper(
new ConsoleLogTarget("console"),
1000,
AsyncLogTargetOverflowAction.Discard));
}
else
{
RemoveTarget("console");
}
}
public static void RemoveTarget(string target)
{
ILogTarget logTarget = GetTarget(target);
@@ -204,7 +215,7 @@ namespace Ryujinx.Common.Logging
_stdErrAdapter.Dispose();
foreach (var target in _logTargets)
foreach (ILogTarget target in _logTargets)
{
target.Dispose();
}
@@ -214,9 +225,9 @@ namespace Ryujinx.Common.Logging
public static IReadOnlyCollection<LogLevel> GetEnabledLevels()
{
var logs = new[] { Debug, Info, Warning, Error, Guest, AccessLog, Stub, Trace };
Log?[] logs = new[] { Debug, Info, Warning, Error, Guest, AccessLog, Stub, Trace };
List<LogLevel> levels = new(logs.Length);
foreach (var log in logs)
foreach (Log? log in logs)
{
if (log.HasValue)
{
@@ -94,7 +94,7 @@ namespace Ryujinx.Common.Logging.Targets
return;
}
using var signal = new ManualResetEventSlim(false);
using ManualResetEventSlim signal = new(false);
try
{
_messageQueue.Add(new FlushEventArgs(signal));
@@ -26,7 +26,7 @@ namespace Ryujinx.Common.Logging.Targets
public void Log(object sender, LogEventArgs e)
{
var logEventArgsJson = LogEventArgsJson.FromLogEventArgs(e);
LogEventArgsJson logEventArgsJson = LogEventArgsJson.FromLogEventArgs(e);
JsonHelper.SerializeToStream(_stream, logEventArgsJson, LogEventJsonSerializerContext.Default.LogEventArgsJson);
}
@@ -19,21 +19,21 @@ namespace Ryujinx.Common
public static byte[] Read(string filename)
{
var (assembly, path) = ResolveManifestPath(filename);
(Assembly assembly, string path) = ResolveManifestPath(filename);
return Read(assembly, path);
}
public static Task<byte[]> ReadAsync(string filename)
{
var (assembly, path) = ResolveManifestPath(filename);
(Assembly assembly, string path) = ResolveManifestPath(filename);
return ReadAsync(assembly, path);
}
public static byte[] Read(Assembly assembly, string filename)
{
using var stream = GetStream(assembly, filename);
using Stream stream = GetStream(assembly, filename);
if (stream == null)
{
return null;
@@ -44,14 +44,14 @@ namespace Ryujinx.Common
public static MemoryOwner<byte> ReadFileToRentedMemory(string filename)
{
var (assembly, path) = ResolveManifestPath(filename);
(Assembly assembly, string path) = ResolveManifestPath(filename);
return ReadFileToRentedMemory(assembly, path);
}
public static MemoryOwner<byte> ReadFileToRentedMemory(Assembly assembly, string filename)
{
using var stream = GetStream(assembly, filename);
using Stream stream = GetStream(assembly, filename);
return stream is null
? null
@@ -60,7 +60,7 @@ namespace Ryujinx.Common
public async static Task<byte[]> ReadAsync(Assembly assembly, string filename)
{
using var stream = GetStream(assembly, filename);
using Stream stream = GetStream(assembly, filename);
if (stream == null)
{
return null;
@@ -71,73 +71,78 @@ namespace Ryujinx.Common
public static string ReadAllText(string filename)
{
var (assembly, path) = ResolveManifestPath(filename);
(Assembly assembly, string path) = ResolveManifestPath(filename);
return ReadAllText(assembly, path);
}
public static Task<string> ReadAllTextAsync(string filename)
{
var (assembly, path) = ResolveManifestPath(filename);
(Assembly assembly, string path) = ResolveManifestPath(filename);
return ReadAllTextAsync(assembly, path);
}
public static string ReadAllText(Assembly assembly, string filename)
{
using var stream = GetStream(assembly, filename);
using Stream stream = GetStream(assembly, filename);
if (stream == null)
{
return null;
}
using var reader = new StreamReader(stream);
using StreamReader reader = new(stream);
return reader.ReadToEnd();
}
public async static Task<string> ReadAllTextAsync(Assembly assembly, string filename)
{
using var stream = GetStream(assembly, filename);
using Stream stream = GetStream(assembly, filename);
if (stream == null)
{
return null;
}
using var reader = new StreamReader(stream);
using StreamReader reader = new(stream);
return await reader.ReadToEndAsync();
}
public static Stream GetStream(string filename)
{
var (assembly, path) = ResolveManifestPath(filename);
(Assembly assembly, string path) = ResolveManifestPath(filename);
return GetStream(assembly, path);
}
public static Stream GetStream(Assembly assembly, string filename)
{
var @namespace = assembly.GetName().Name;
var manifestUri = @namespace + "." + filename.Replace('/', '.');
string @namespace = assembly.GetName().Name;
string manifestUri = @namespace + "." + filename.Replace('/', '.');
var stream = assembly.GetManifestResourceStream(manifestUri);
Stream stream = assembly.GetManifestResourceStream(manifestUri);
return stream;
}
public static string[] GetAllAvailableResources(string path, string ext = "")
{
return ResolveManifestPath(path).Item1.GetManifestResourceNames()
.Where(r => r.EndsWith(ext))
(Assembly assembly, string resourcePath) = ResolveManifestPath(path);
string manifestPath = assembly.GetName().Name + "." + resourcePath.Replace('/', '.');
return assembly.GetManifestResourceNames()
.Where(r => r.StartsWith(manifestPath + ".", StringComparison.Ordinal))
.Where(r => r.EndsWith(ext, StringComparison.Ordinal))
.ToArray();
}
private static (Assembly, string) ResolveManifestPath(string filename)
{
var segments = filename.Split('/', 2, StringSplitOptions.RemoveEmptyEntries);
string[] segments = filename.Split('/', 2, StringSplitOptions.RemoveEmptyEntries);
if (segments.Length >= 2)
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.GetName().Name == segments[0])
{
@@ -9,7 +9,7 @@ namespace Ryujinx.Common.Utilities
public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive)
{
// Get information about the source directory
var dir = new DirectoryInfo(sourceDir);
DirectoryInfo dir = new(sourceDir);
// Check if the source directory exists
if (!dir.Exists)
@@ -49,7 +49,7 @@ namespace Ryujinx.Common.Utilities
public static string SanitizeFileName(string fileName)
{
var reservedChars = new HashSet<char>(Path.GetInvalidFileNameChars());
HashSet<char> reservedChars = new(Path.GetInvalidFileNameChars());
return string.Concat(fileName.Select(c => reservedChars.Contains(c) ? '_' : c));
}
}
@@ -1,5 +1,6 @@
using MsgPack;
using System;
using System.Collections.Generic;
using System.Text;
namespace Ryujinx.Common.Utilities
@@ -18,7 +19,7 @@ namespace Ryujinx.Common.Utilities
public static string Format(MessagePackObject obj)
{
var builder = new IndentedStringBuilder();
IndentedStringBuilder builder = new();
FormatMsgPackObj(obj, builder);
@@ -41,7 +42,7 @@ namespace Ryujinx.Common.Utilities
}
else
{
var literal = obj.ToObject();
object literal = obj.ToObject();
if (literal is String)
{
@@ -88,7 +89,7 @@ namespace Ryujinx.Common.Utilities
{
builder.Append("[ ");
foreach (var b in arr)
foreach (byte b in arr)
{
builder.Append("0x");
builder.Append(ToHexChar(b >> 4));
@@ -111,7 +112,7 @@ namespace Ryujinx.Common.Utilities
builder.Append("0x");
}
foreach (var b in arr)
foreach (byte b in arr)
{
builder.Append(ToHexChar(b >> 4));
builder.Append(ToHexChar(b & 0xF));
@@ -122,7 +123,7 @@ namespace Ryujinx.Common.Utilities
private static void FormatMsgPackMap(MessagePackObject obj, IndentedStringBuilder builder)
{
var map = obj.AsDictionary();
MessagePackObjectDictionary map = obj.AsDictionary();
builder.Append('{');
@@ -130,7 +131,7 @@ namespace Ryujinx.Common.Utilities
builder.IncreaseIndent()
.AppendLine();
foreach (var item in map)
foreach (KeyValuePair<MessagePackObject, MessagePackObject> item in map)
{
FormatMsgPackObj(item.Key, builder);
@@ -154,11 +155,11 @@ namespace Ryujinx.Common.Utilities
private static void FormatMsgPackArray(MessagePackObject obj, IndentedStringBuilder builder)
{
var arr = obj.AsList();
IList<MessagePackObject> arr = obj.AsList();
builder.Append("[ ");
foreach (var item in arr)
foreach (MessagePackObject item in arr)
{
FormatMsgPackObj(item, builder);
+2 -1
View File
@@ -1,5 +1,6 @@
using Microsoft.IO;
using Ryujinx.Common.Memory;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@@ -27,7 +28,7 @@ namespace Ryujinx.Common.Utilities
MemoryOwner<byte> ownedMemory = MemoryOwner<byte>.Rent(checked((int)bytesExpected));
var destSpan = ownedMemory.Span;
Span<byte> destSpan = ownedMemory.Span;
int totalBytesRead = 0;
@@ -15,7 +15,7 @@ namespace Ryujinx.Common.Utilities
{
internal static TimeSpan Measure(Action action)
{
var sw = new Stopwatch();
Stopwatch sw = new();
sw.Start();
try
@@ -66,7 +66,7 @@ namespace Ryujinx.Common.Utilities
{
if (Path.GetExtension(filename).Equals(".XCI", StringComparison.InvariantCultureIgnoreCase))
{
var trimmer = new XCIFileTrimmer(filename, log);
XCIFileTrimmer trimmer = new(filename, log);
return trimmer.CanBeTrimmed;
}
@@ -77,7 +77,7 @@ namespace Ryujinx.Common.Utilities
{
if (Path.GetExtension(filename).Equals(".XCI", StringComparison.InvariantCultureIgnoreCase))
{
var trimmer = new XCIFileTrimmer(filename, log);
XCIFileTrimmer trimmer = new(filename, log);
return trimmer.CanBeUntrimmed;
}
@@ -221,7 +221,7 @@ namespace Ryujinx.Common.Utilities
{
long maxReads = readSizeB / BufferSize;
long read = 0;
var buffer = new byte[BufferSize];
byte[] buffer = new byte[BufferSize];
while (true)
{
@@ -287,7 +287,7 @@ namespace Ryujinx.Common.Utilities
try
{
var info = new FileInfo(Filename);
FileInfo info = new(Filename);
if ((info.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
{
try
@@ -308,7 +308,7 @@ namespace Ryujinx.Common.Utilities
return OperationOutcome.FileSizeChanged;
}
var outfileStream = new FileStream(_filename, FileMode.Open, FileAccess.Write, FileShare.Write);
FileStream outfileStream = new(_filename, FileMode.Open, FileAccess.Write, FileShare.Write);
try
{
@@ -347,7 +347,7 @@ namespace Ryujinx.Common.Utilities
{
Log?.Write(LogType.Info, "Untrimming...");
var info = new FileInfo(Filename);
FileInfo info = new(Filename);
if ((info.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
{
try
@@ -368,7 +368,7 @@ namespace Ryujinx.Common.Utilities
return OperationOutcome.FileSizeChanged;
}
var outfileStream = new FileStream(_filename, FileMode.Append, FileAccess.Write, FileShare.Write);
FileStream outfileStream = new(_filename, FileMode.Append, FileAccess.Write, FileShare.Write);
long bytesToWriteB = UntrimmedFileSizeB - FileSizeB;
try
@@ -413,7 +413,7 @@ namespace Ryujinx.Common.Utilities
try
{
var buffer = new byte[BufferSize];
byte[] buffer = new byte[BufferSize];
Array.Fill<byte>(buffer, PaddingByte);
while (bytesLeftToWriteB > 0)
+35 -296
View File
@@ -1,15 +1,9 @@
using ARMeilleure.Memory;
using Ryujinx.Common;
using Ryujinx.Cpu.Signal;
using Ryujinx.Memory;
using ARMeilleure.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using static Ryujinx.Cpu.MemoryEhMeilleure;
namespace ARMeilleure.Common
namespace Ryujinx.Cpu
{
/// <summary>
/// Represents a table of guest address to a value.
@@ -17,84 +11,12 @@ namespace ARMeilleure.Common
/// <typeparam name="TEntry">Type of the value</typeparam>
public unsafe class AddressTable<TEntry> : IAddressTable<TEntry> where TEntry : unmanaged
{
/// <summary>
/// Represents a page of the address table.
/// </summary>
private readonly struct AddressTablePage
{
/// <summary>
/// True if the allocation belongs to a sparse block, false otherwise.
/// </summary>
public readonly bool IsSparse;
/// <summary>
/// Base address for the page.
/// </summary>
public readonly nint Address;
public AddressTablePage(bool isSparse, nint address)
{
IsSparse = isSparse;
Address = address;
}
}
/// <summary>
/// A sparsely mapped block of memory with a signal handler to map pages as they're accessed.
/// </summary>
private readonly struct TableSparseBlock : IDisposable
{
public readonly SparseMemoryBlock Block;
private readonly TrackingEventDelegate _trackingEvent;
public TableSparseBlock(ulong size, Action<nint> ensureMapped, PageInitDelegate pageInit)
{
var block = new SparseMemoryBlock(size, pageInit, null);
_trackingEvent = (ulong address, ulong _, bool _) =>
{
ulong pointer = (ulong)block.Block.Pointer + address;
ensureMapped((nint)pointer);
return pointer;
};
bool added = NativeSignalHandler.AddTrackedRegion(
(nuint)block.Block.Pointer,
(nuint)(block.Block.Pointer + (nint)block.Block.Size),
Marshal.GetFunctionPointerForDelegate(_trackingEvent));
if (!added)
{
throw new InvalidOperationException("Number of allowed tracked regions exceeded.");
}
Block = block;
}
public void Dispose()
{
NativeSignalHandler.RemoveTrackedRegion((nuint)Block.Block.Pointer);
Block.Dispose();
}
}
private bool _disposed;
private TEntry** _table;
private readonly List<AddressTablePage> _pages;
private TEntry _fill;
private readonly List<nint> _pages;
private readonly MemoryBlock _sparseFill;
private readonly SparseMemoryBlock _fillBottomLevel;
private readonly TEntry* _fillBottomLevelPtr;
private readonly List<TableSparseBlock> _sparseReserved;
private readonly ReaderWriterLockSlim _sparseLock;
private ulong _sparseBlockSize;
private ulong _sparseReservedOffset;
public bool Sparse { get; }
/// <inheritdoc/>
public AddressTableType TableType => AddressTableType.Default;
/// <inheritdoc/>
public ulong Mask { get; }
@@ -103,17 +25,7 @@ namespace ARMeilleure.Common
public AddressTableLevel[] Levels { get; }
/// <inheritdoc/>
public TEntry Fill
{
get
{
return _fill;
}
set
{
UpdateFill(value);
}
}
public TEntry Fill { get; set; }
/// <inheritdoc/>
public nint Base
@@ -131,44 +43,24 @@ namespace ARMeilleure.Common
/// <summary>
/// Constructs a new instance of the <see cref="AddressTable{TEntry}"/> class with the specified list of
/// <see cref="Level"/>.
/// <see cref="AddressTableLevel"/>.
/// </summary>
/// <param name="levels">Levels for the address table</param>
/// <param name="sparse">True if the bottom page should be sparsely mapped</param>
/// <exception cref="ArgumentNullException"><paramref name="levels"/> is null</exception>
/// <exception cref="ArgumentException">Length of <paramref name="levels"/> is less than 2</exception>
public AddressTable(AddressTableLevel[] levels, bool sparse)
public AddressTable(AddressTableLevel[] levels)
{
ArgumentNullException.ThrowIfNull(levels);
_pages = new List<AddressTablePage>(capacity: 16);
_pages = new List<nint>(capacity: 16);
Levels = levels;
Mask = 0;
foreach (var level in Levels)
foreach (AddressTableLevel level in Levels)
{
Mask |= level.Mask;
}
Sparse = sparse;
if (sparse)
{
// If the address table is sparse, allocate a fill block
_sparseFill = new MemoryBlock(268435456ul, MemoryAllocationFlags.Mirrorable); //low Power TC uses size: 65536ul
ulong bottomLevelSize = (1ul << levels.Last().Length) * (ulong)sizeof(TEntry);
_fillBottomLevel = new SparseMemoryBlock(bottomLevelSize, null, _sparseFill);
_fillBottomLevelPtr = (TEntry*)_fillBottomLevel.Block.Pointer;
_sparseReserved = [];
_sparseLock = new ReaderWriterLockSlim();
_sparseBlockSize = bottomLevelSize;
}
}
/// <summary>
@@ -176,29 +68,10 @@ namespace ARMeilleure.Common
/// Selects the best table structure for A32/A64, taking into account the selected memory manager type.
/// </summary>
/// <param name="for64Bits">True if the guest is A64, false otherwise</param>
/// <param name="type">Memory manager type</param>
/// <returns>An <see cref="AddressTable{TEntry}"/> for ARM function lookup</returns>
public static AddressTable<TEntry> CreateForArm(bool for64Bits, MemoryManagerType type)
public static AddressTable<TEntry> CreateForArm(bool for64Bits)
{
// Assume software memory means that we don't want to use any signal handlers.
bool sparse = type != MemoryManagerType.SoftwareMmu && type != MemoryManagerType.SoftwarePageTable;
return new AddressTable<TEntry>(AddressTablePresets.GetArmPreset(for64Bits, sparse), sparse);
}
/// <summary>
/// Update the fill value for the bottom level of the table.
/// </summary>
/// <param name="fillValue">New fill value</param>
private void UpdateFill(TEntry fillValue)
{
if (_sparseFill != null)
{
Span<byte> span = _sparseFill.GetSpan(0, (int)_sparseFill.Size);
MemoryMarshal.Cast<byte, TEntry>(span).Fill(fillValue);
}
_fill = fillValue;
return new AddressTable<TEntry>(AddressTablePresets.GetArmPreset(for64Bits, false));
}
/// <summary>
@@ -206,17 +79,7 @@ namespace ARMeilleure.Common
/// </summary>
/// <param name="address"></param>
/// <param name="size"></param>
public void SignalCodeRange(ulong address, ulong size)
{
AddressTableLevel bottom = Levels.Last();
ulong bottomLevelEntries = 1ul << bottom.Length;
ulong entryIndex = address >> bottom.Index;
ulong entries = size >> bottom.Index;
entries += entryIndex - BitUtils.AlignDown(entryIndex, bottomLevelEntries);
_sparseBlockSize = Math.Max(_sparseBlockSize, BitUtils.AlignUp(entries, bottomLevelEntries) * (ulong)sizeof(TEntry));
}
public void SignalCodeRange(ulong address, ulong size) { }
/// <inheritdoc/>
public bool IsValid(ulong address)
@@ -240,8 +103,6 @@ namespace ARMeilleure.Common
long index = Levels[^1].GetValue(address);
EnsureMapped((nint)(page + index));
return ref page[index];
}
}
@@ -260,19 +121,19 @@ namespace ARMeilleure.Common
ref AddressTableLevel level = ref Levels[i];
if (page != null)
{
ref TEntry* nextPage = ref page[level.GetValue(address)];
ref TEntry* nextPage = ref page![level.GetValue(address)];
if (nextPage == null || nextPage == _fillBottomLevelPtr)
if (nextPage == null)
{
ref AddressTableLevel nextLevel = ref Levels[i + 1];
if (i == Levels.Length - 2)
{
nextPage = (TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true);
nextPage = (TEntry*)Allocate(1 << nextLevel.Length, Fill);
}
else
{
nextPage = (TEntry*)Allocate(1 << nextLevel.Length, GetFillValue(i), leaf: false);
nextPage = (TEntry*)Allocate(1 << nextLevel.Length, nint.Zero);
}
}
@@ -283,57 +144,6 @@ namespace ARMeilleure.Common
return (TEntry*)page;
}
/// <summary>
/// Ensure the given pointer is mapped in any overlapping sparse reservations.
/// </summary>
/// <param name="ptr">Pointer to be mapped</param>
private void EnsureMapped(nint ptr)
{
if (Sparse)
{
// Check sparse allocations to see if the pointer is in any of them.
// Ensure the page is committed if there's a match.
_sparseLock.EnterReadLock();
try
{
foreach (TableSparseBlock reserved in _sparseReserved)
{
SparseMemoryBlock sparse = reserved.Block;
if (ptr >= sparse.Block.Pointer && ptr < sparse.Block.Pointer + (nint)sparse.Block.Size)
{
sparse.EnsureMapped((ulong)(ptr - sparse.Block.Pointer));
break;
}
}
}
finally
{
_sparseLock.ExitReadLock();
}
}
}
/// <summary>
/// Get the fill value for a non-leaf level of the table.
/// </summary>
/// <param name="level">Level to get the fill value for</param>
/// <returns>The fill value</returns>
private nint GetFillValue(int level)
{
if (_fillBottomLevel != null && level == Levels.Length - 2)
{
return (nint)_fillBottomLevelPtr;
}
else
{
return nint.Zero;
}
}
/// <summary>
/// Lazily initialize and get the root page of the <see cref="AddressTable{TEntry}"/>.
/// </summary>
@@ -343,91 +153,35 @@ namespace ARMeilleure.Common
if (_table == null)
{
if (Levels.Length == 1)
_table = (TEntry**)Allocate(1 << Levels[0].Length, Fill, leaf: true);
_table = (TEntry**)Allocate(1 << Levels[0].Length, Fill);
else
_table = (TEntry**)Allocate(1 << Levels[0].Length, GetFillValue(0), leaf: false);
_table = (TEntry**)Allocate(1 << Levels[0].Length, nint.Zero);
}
return _table;
}
/// <summary>
/// Initialize a leaf page with the fill value.
/// </summary>
/// <param name="page">Page to initialize</param>
private void InitLeafPage(Span<byte> page)
{
MemoryMarshal.Cast<byte, TEntry>(page).Fill(_fill);
}
/// <summary>
/// Reserve a new sparse block, and add it to the list.
/// </summary>
/// <returns>The new sparse block that was added</returns>
private TableSparseBlock ReserveNewSparseBlock()
{
var block = new TableSparseBlock(_sparseBlockSize, EnsureMapped, InitLeafPage);
_sparseReserved.Add(block);
_sparseReservedOffset = 0;
return block;
}
/// <summary>
/// Allocates a block of memory of the specified type and length.
/// </summary>
/// <typeparam name="T">Type of elements</typeparam>
/// <param name="length">Number of elements</param>
/// <param name="fill">Fill value</param>
/// <param name="leaf"><see langword="true"/> if leaf; otherwise <see langword="false"/></param>
/// <returns>Allocated block</returns>
private nint Allocate<T>(int length, T fill, bool leaf) where T : unmanaged
private nint Allocate<T>(int length, T fill) where T : unmanaged
{
var size = sizeof(T) * length;
int size = sizeof(T) * length;
AddressTablePage page;
nint address = (nint)NativeAllocator.Instance.Allocate((uint)size);
if (Sparse && leaf)
{
_sparseLock.EnterWriteLock();
Span<T> span = new((void*)address, length);
span.Fill(fill);
SparseMemoryBlock block;
if (_sparseReserved.Count == 0)
{
block = ReserveNewSparseBlock().Block;
}
else
{
block = _sparseReserved.Last().Block;
if (_sparseReservedOffset == block.Block.Size)
{
block = ReserveNewSparseBlock().Block;
}
}
page = new AddressTablePage(true, block.Block.Pointer + (nint)_sparseReservedOffset);
_sparseReservedOffset += (ulong)size;
_sparseLock.ExitWriteLock();
}
else
{
var address = (nint)NativeAllocator.Instance.Allocate((uint)size);
page = new AddressTablePage(false, address);
var span = new Span<T>((void*)page.Address, length);
span.Fill(fill);
}
_pages.Add(page);
_pages.Add(address);
//TranslatorEventSource.Log.AddressTableAllocated(size, leaf);
return page.Address;
return address;
}
/// <summary>
@@ -446,32 +200,17 @@ namespace ARMeilleure.Common
/// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resouces</param>
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
if (_disposed)
{
foreach (var page in _pages)
{
if (!page.IsSparse)
{
Marshal.FreeHGlobal(page.Address);
}
}
if (Sparse)
{
foreach (TableSparseBlock block in _sparseReserved)
{
block.Dispose();
}
_sparseReserved.Clear();
_fillBottomLevel.Dispose();
_sparseFill.Dispose();
_sparseLock.Dispose();
}
_disposed = true;
return;
}
foreach (nint page in _pages)
{
Marshal.FreeHGlobal(page);
}
_disposed = true;
}
/// <summary>
+1 -1
View File
@@ -29,7 +29,7 @@ namespace Ryujinx.Cpu.AppleHv
public HvAddressSpace(MemoryBlock backingMemory, ulong asSize)
{
(_asBase, var ipaAllocator) = HvVm.CreateAddressSpace(backingMemory);
(_asBase, HvIpaAllocator ipaAllocator) = HvVm.CreateAddressSpace(backingMemory);
_backingSize = backingMemory.Size;
_userRange = new HvAddressSpaceRange(ipaAllocator);
+2 -1
View File
@@ -1,4 +1,5 @@
using ARMeilleure.Memory;
using ARMeilleure.Translation.PTC;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv
@@ -32,7 +33,7 @@ namespace Ryujinx.Cpu.AppleHv
{
}
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
public IDiskCacheLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled)
{
return new DummyDiskCacheLoadState();
}
+53 -50
View File
@@ -1,4 +1,5 @@
using ARMeilleure.State;
using Ryujinx.Common.Logging;
using Ryujinx.Cpu.AppleHv.Arm;
using Ryujinx.Memory.Tracking;
using System;
@@ -17,9 +18,7 @@ namespace Ryujinx.Cpu.AppleHv
{
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
if (currentEl == (uint)ExceptionLevel.EL1h)
{
return _impl.ElrEl1;
}
return _impl.Pc;
}
}
@@ -69,9 +68,7 @@ namespace Ryujinx.Cpu.AppleHv
set
{
if (value)
{
throw new NotSupportedException();
}
}
}
@@ -82,11 +79,13 @@ namespace Ryujinx.Cpu.AppleHv
private readonly IHvExecutionContext _shadowContext;
private IHvExecutionContext _impl;
private int _shouldStep;
private readonly ExceptionCallbacks _exceptionCallbacks;
private int _interruptRequested;
// GPU Sync control
private int _syncCounter;
private int _strongSyncCounter;
public HvExecutionContext(ICounter counter, ExceptionCallbacks exceptionCallbacks)
{
_counter = counter;
@@ -108,38 +107,17 @@ namespace Ryujinx.Cpu.AppleHv
/// <inheritdoc/>
public void SetV(int index, V128 value) => _impl.SetV(index, value);
private void InterruptHandler()
{
_exceptionCallbacks.InterruptCallback?.Invoke(this);
}
private void BreakHandler(ulong address, int imm)
{
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
}
private void StepHandler()
{
_exceptionCallbacks.StepCallback?.Invoke(this);
}
private void SupervisorCallHandler(ulong address, int imm)
{
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
}
private void UndefinedHandler(ulong address, int opCode)
{
_exceptionCallbacks.UndefinedCallback?.Invoke(this, address, opCode);
}
private void InterruptHandler() => _exceptionCallbacks.InterruptCallback?.Invoke(this);
private void BreakHandler(ulong address, int imm) => _exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
private void StepHandler() => _exceptionCallbacks.StepCallback?.Invoke(this);
private void SupervisorCallHandler(ulong address, int imm) => _exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
private void UndefinedHandler(ulong address, int opCode) => _exceptionCallbacks.UndefinedCallback?.Invoke(this, address, opCode);
/// <inheritdoc/>
public void RequestInterrupt()
{
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0 && _impl is HvExecutionContextVcpu impl)
{
impl.RequestInterrupt();
}
}
private bool GetAndClearInterruptRequested()
@@ -161,13 +139,9 @@ namespace Ryujinx.Cpu.AppleHv
{
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
if (currentEl == (uint)ExceptionLevel.EL1h)
{
_impl.ElrEl1 = value;
}
else
{
_impl.Pc = value;
}
}
}
@@ -181,9 +155,11 @@ namespace Ryujinx.Cpu.AppleHv
public unsafe void Execute(HvMemoryManager memoryManager, ulong address)
{
HvVcpu vcpu = HvVcpuPool.Instance.Create(memoryManager.AddressSpace, _shadowContext, SwapContext);
HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError();
_syncCounter = 0;
_strongSyncCounter = 0;
while (Running)
{
if (Interlocked.CompareExchange(ref _shouldStep, 0, 1) == 1)
@@ -192,16 +168,23 @@ namespace Ryujinx.Cpu.AppleHv
if (currentEl == (uint)ExceptionLevel.EL1h)
{
HvApi.hv_vcpu_get_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
spsr |= (1 << 21);
spsr |= (1U << 21);
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, spsr);
}
else
{
Pstate |= (1 << 21);
Pstate |= (1U << 21);
}
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.MDSCR_EL1, 1);
}
// Adaptive GPU synchronization to prevent 0 FPS
if (++_syncCounter % 12 == 0)
{
TryGpuSync();
_syncCounter = 0;
}
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
HvExitReason reason = vcpu.ExitInfo->Reason;
@@ -212,9 +195,7 @@ namespace Ryujinx.Cpu.AppleHv
ExceptionClass hvEc = (ExceptionClass)(hvEsr >> 26);
if (hvEc != ExceptionClass.HvcAarch64)
{
throw new Exception($"Unhandled exception from guest kernel with ESR 0x{hvEsr:X} ({hvEc}).");
}
address = SynchronousException(memoryManager, ref vcpu);
HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError();
@@ -245,10 +226,31 @@ namespace Ryujinx.Cpu.AppleHv
HvVcpuPool.Instance.Destroy(vcpu, SwapContext);
}
// TryGpuSync() is called periodically in the main Execute() loop. The "syncing" value can be tuned based on gameplay results.
// This feature it to be followed-up and further completed in a future PR.
private void TryGpuSync()
{
try
{
Thread.Yield();
if (++_strongSyncCounter % 6 == 0)
{
Thread.Yield();
}
}
catch (Exception ex)
{
if (_strongSyncCounter % 100 == 0)
{
Logger.Warning?.Print(LogClass.Gpu, $"[AppleHv] GPU sync issue: {ex.Message}");
}
}
}
private ulong SynchronousException(HvMemoryManager memoryManager, ref HvVcpu vcpu)
{
ulong vcpuHandle = vcpu.Handle;
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.ELR_EL1, out ulong elr).ThrowOnError();
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.ESR_EL1, out ulong esr).ThrowOnError();
@@ -259,16 +261,20 @@ namespace Ryujinx.Cpu.AppleHv
case ExceptionClass.DataAbortLowerEl:
DataAbort(memoryManager.Tracking, vcpuHandle, (uint)esr);
break;
case ExceptionClass.TrappedMsrMrsSystem:
InstructionTrap((uint)esr);
HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.ELR_EL1, elr + 4UL).ThrowOnError();
break;
case ExceptionClass.SvcAarch64:
ReturnToPool(vcpu);
ushort id = (ushort)esr;
SupervisorCallHandler(elr - 4UL, id);
Thread.Yield(); // MoltenVK causes extremely frequent SVC exits, and HVF handles them in a busy loop. Hypervisor.Framework accelerates the guest CPU, and without periodic yielding/flushing, MoltenVK's presentation queue can starve, causing permanent 0 FPS deadlock.
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
break;
case ExceptionClass.SoftwareStepLowerEl:
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
spsr &= ~((ulong)(1 << 21));
@@ -278,21 +284,23 @@ namespace Ryujinx.Cpu.AppleHv
StepHandler();
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
break;
case ExceptionClass.BrkAarch64:
ReturnToPool(vcpu);
BreakHandler(elr, (ushort)esr);
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
break;
default:
throw new Exception($"Unhandled guest exception {ec}.");
}
// Make sure we will continue running at EL0.
if (memoryManager.AddressSpace.GetAndClearUserTlbInvalidationPending())
{
// TODO: Invalidate only the range that was modified?
return HvAddressSpace.KernelRegionTlbiEretAddress;
}
return HvAddressSpace.KernelRegionEretAddress;
}
@@ -305,7 +313,6 @@ namespace Ryujinx.Cpu.AppleHv
if (farValid)
{
HvApi.hv_vcpu_get_sys_reg(vcpu, HvSysReg.FAR_EL1, out ulong far).ThrowOnError();
ulong size = 1UL << accessSizeLog2;
if (!tracking.VirtualMemoryEvent(far, size, write))
@@ -349,9 +356,7 @@ namespace Ryujinx.Cpu.AppleHv
private void WriteRt(uint rt, ulong value)
{
if (rt < 31)
{
SetX((int)rt, value);
}
}
private void ReturnToPool(HvVcpu vcpu)
@@ -369,8 +374,6 @@ namespace Ryujinx.Cpu.AppleHv
_impl = newContext;
}
public void Dispose()
{
}
public void Dispose() { }
}
}
+254 -91
View File
@@ -1,5 +1,7 @@
using ARMeilleure.State;
using Ryujinx.Common.Logging;
using Ryujinx.Memory;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
@@ -14,8 +16,31 @@ namespace Ryujinx.Cpu.AppleHv
private static readonly SetSimdFpReg _setSimdFpReg;
private static readonly nint _setSimdFpRegNativePtr;
public static bool AggressiveMode { get; set; } = false;
private bool _earlyBootPhase = true;
public ulong ThreadUid { get; set; }
private readonly ulong[] _x = new ulong[32];
private readonly V128[] _v = new V128[32];
private ulong _pc;
private ulong _elrEl1;
private ulong _esrEl1;
private ulong _tpidrEl0;
private ulong _tpidrroEl0;
private ulong _fpcr;
private ulong _fpsr;
private ulong _pstateRaw;
private long _fallbackCount;
private long _lastWarningTicks;
private const long WarningCooldownTicks = 500_000_000; // 0.5 seconds
private readonly ulong _vcpu;
private int _interruptRequested;
private readonly object _registerLock = new object();
static HvExecutionContextVcpu()
{
// .NET does not support passing vectors by value, so we need to pass a pointer and use a native
@@ -33,155 +58,293 @@ namespace Ryujinx.Cpu.AppleHv
}
}
public HvExecutionContextVcpu(ulong vcpu)
{
_vcpu = vcpu;
Reset();
}
public void Reset()
{
lock (_registerLock)
{
_pstateRaw = 0x80000000UL;
_pc = 0;
_elrEl1 = 0;
_esrEl1 = 0;
_tpidrEl0 = 0;
_tpidrroEl0 = 0;
_fpcr = 0;
_fpsr = 0;
Array.Clear(_x, 0, _x.Length);
Array.Clear(_v, 0, _v.Length);
_fallbackCount = 0;
_lastWarningTicks = 0;
_interruptRequested = 0;
_earlyBootPhase = true;
}
}
private void LogHvWarning(string operation, string regName, string extra = "")
{
if (AggressiveMode) return;
long now = DateTime.UtcNow.Ticks;
if (now - _lastWarningTicks <= WarningCooldownTicks) return;
string msg = $"[AppleHv] BadArgument on {operation} {regName} | PC=0x{_pc:X16}";
if (!string.IsNullOrEmpty(extra)) msg += $" | {extra}";
msg += $" | Total: {Interlocked.Read(ref _fallbackCount)}";
Logger.Warning?.Print(LogClass.Cpu, msg);
_lastWarningTicks = now;
}
public ulong Pc
{
get
{
HvApi.hv_vcpu_get_reg(_vcpu, HvReg.PC, out ulong pc).ThrowOnError();
return pc;
}
set
{
HvApi.hv_vcpu_set_reg(_vcpu, HvReg.PC, value).ThrowOnError();
}
get { lock (_registerLock) return GetRegCached(HvReg.PC, ref _pc, "PC"); }
set { lock (_registerLock) SetRegCached(HvReg.PC, value, ref _pc, "PC"); }
}
public ulong ElrEl1
{
get
{
HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.ELR_EL1, out ulong elr).ThrowOnError();
return elr;
}
set
{
HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.ELR_EL1, value).ThrowOnError();
}
get { lock (_registerLock) return GetSysRegCached(HvSysReg.ELR_EL1, ref _elrEl1, "ELR_EL1"); }
set { lock (_registerLock) SetSysRegCached(HvSysReg.ELR_EL1, value, ref _elrEl1, "ELR_EL1"); }
}
public ulong EsrEl1
{
get
{
HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.ESR_EL1, out ulong esr).ThrowOnError();
return esr;
}
set
{
HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.ESR_EL1, value).ThrowOnError();
}
get { lock (_registerLock) return GetSysRegCached(HvSysReg.ESR_EL1, ref _esrEl1, "ESR_EL1"); }
set { lock (_registerLock) SetSysRegCached(HvSysReg.ESR_EL1, value, ref _esrEl1, "ESR_EL1"); }
}
public long TpidrEl0
{
get
{
HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.TPIDR_EL0, out ulong tpidrEl0).ThrowOnError();
return (long)tpidrEl0;
}
set
{
HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.TPIDR_EL0, (ulong)value).ThrowOnError();
}
get { lock (_registerLock) return (long)GetSysRegCached(HvSysReg.TPIDR_EL0, ref _tpidrEl0, "TPIDR_EL0"); }
set { lock (_registerLock) SetSysRegCached(HvSysReg.TPIDR_EL0, (ulong)value, ref _tpidrEl0, "TPIDR_EL0"); }
}
public long TpidrroEl0
{
get
{
HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.TPIDRRO_EL0, out ulong tpidrroEl0).ThrowOnError();
return (long)tpidrroEl0;
}
set
{
HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.TPIDRRO_EL0, (ulong)value).ThrowOnError();
}
get { lock (_registerLock) return (long)GetSysRegCached(HvSysReg.TPIDRRO_EL0, ref _tpidrroEl0, "TPIDRRO_EL0"); }
set { lock (_registerLock) SetSysRegCached(HvSysReg.TPIDRRO_EL0, (ulong)value, ref _tpidrroEl0, "TPIDRRO_EL0"); }
}
public uint Pstate
{
get
{
HvApi.hv_vcpu_get_reg(_vcpu, HvReg.CPSR, out ulong cpsr).ThrowOnError();
return (uint)cpsr;
lock (_registerLock)
{
HvResult res = HvApi.hv_vcpu_get_reg(_vcpu, HvReg.CPSR, out ulong val);
if (res == HvResult.BadArgument)
{
Interlocked.Increment(ref _fallbackCount);
LogHvWarning("Get", "CPSR (Pstate)");
return (uint)_pstateRaw;
}
res.ThrowOnError();
_pstateRaw = val;
return (uint)val;
}
}
set
{
HvApi.hv_vcpu_set_reg(_vcpu, HvReg.CPSR, (ulong)value).ThrowOnError();
lock (_registerLock)
{
HvResult res = HvApi.hv_vcpu_set_reg(_vcpu, HvReg.CPSR, value);
if (res == HvResult.BadArgument)
{
Interlocked.Increment(ref _fallbackCount);
LogHvWarning("Set", "CPSR (Pstate)", $"value=0x{value:X}");
}
else res.ThrowOnError();
_pstateRaw = value;
}
}
}
public uint Fpcr
{
get
{
HvApi.hv_vcpu_get_reg(_vcpu, HvReg.FPCR, out ulong fpcr).ThrowOnError();
return (uint)fpcr;
}
set
{
HvApi.hv_vcpu_set_reg(_vcpu, HvReg.FPCR, (ulong)value).ThrowOnError();
}
get { lock (_registerLock) return (uint)GetRegCached(HvReg.FPCR, ref _fpcr, "FPCR"); }
set { lock (_registerLock) SetRegCached(HvReg.FPCR, value, ref _fpcr, "FPCR"); }
}
public uint Fpsr
{
get
{
HvApi.hv_vcpu_get_reg(_vcpu, HvReg.FPSR, out ulong fpsr).ThrowOnError();
return (uint)fpsr;
}
set
{
HvApi.hv_vcpu_set_reg(_vcpu, HvReg.FPSR, (ulong)value).ThrowOnError();
}
}
private readonly ulong _vcpu;
private int _interruptRequested;
public HvExecutionContextVcpu(ulong vcpu)
{
_vcpu = vcpu;
get { lock (_registerLock) return (uint)GetRegCached(HvReg.FPSR, ref _fpsr, "FPSR"); }
set { lock (_registerLock) SetRegCached(HvReg.FPSR, value, ref _fpsr, "FPSR"); }
}
public ulong GetX(int index)
{
if (index == 31)
lock (_registerLock)
{
HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.SP_EL0, out ulong value).ThrowOnError();
return value;
}
else
{
HvApi.hv_vcpu_get_reg(_vcpu, HvReg.X0 + (uint)index, out ulong value).ThrowOnError();
return value;
ulong value;
string regName = index == 31 ? "SP_EL0" : $"X{index}";
if (index == 31)
{
HvResult res = HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.SP_EL0, out value);
if (res == HvResult.BadArgument)
{
Interlocked.Increment(ref _fallbackCount);
LogHvWarning("GetX", regName);
return _x[31];
}
res.ThrowOnError();
return _x[31] = value;
}
if ((uint)index > 30) return 0;
if (index == 0 && _earlyBootPhase && _pc == 0)
{
return _x[0];
}
HvResult resX = HvApi.hv_vcpu_get_reg(_vcpu, HvReg.X0 + (uint)index, out value);
if (resX == HvResult.BadArgument)
{
Interlocked.Increment(ref _fallbackCount);
LogHvWarning("GetX", regName);
return _x[index];
}
resX.ThrowOnError();
return _x[index] = value;
}
}
public void SetX(int index, ulong value)
{
if (index == 31)
lock (_registerLock)
{
HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.SP_EL0, value).ThrowOnError();
}
else
{
HvApi.hv_vcpu_set_reg(_vcpu, HvReg.X0 + (uint)index, value).ThrowOnError();
string regName = index == 31 ? "SP_EL0" : $"X{index}";
if (index == 31)
{
HvResult res = HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.SP_EL0, value);
if (res == HvResult.BadArgument)
{
Interlocked.Increment(ref _fallbackCount);
LogHvWarning("SetX", regName, $"value=0x{value:X16}");
_x[31] = value;
return;
}
res.ThrowOnError();
_x[31] = value;
}
else if ((uint)index <= 30)
{
HvResult res = HvApi.hv_vcpu_set_reg(_vcpu, HvReg.X0 + (uint)index, value);
if (res == HvResult.BadArgument)
{
Interlocked.Increment(ref _fallbackCount);
LogHvWarning("SetX", regName, $"value=0x{value:X16}");
_x[index] = value;
return;
}
res.ThrowOnError();
_x[index] = value;
}
}
}
public V128 GetV(int index)
{
HvApi.hv_vcpu_get_simd_fp_reg(_vcpu, HvSimdFPReg.Q0 + (uint)index, out HvSimdFPUchar16 value).ThrowOnError();
return new V128(value.Low, value.High);
lock (_registerLock)
{
if ((uint)index > 31) return default;
HvResult res = HvApi.hv_vcpu_get_simd_fp_reg(_vcpu, HvSimdFPReg.Q0 + (uint)index, out HvSimdFPUchar16 val);
if (res == HvResult.BadArgument)
{
Interlocked.Increment(ref _fallbackCount);
LogHvWarning("GetV", $"Q{index}");
return _v[index];
}
res.ThrowOnError();
return _v[index] = new V128(val.Low, val.High);
}
}
public void SetV(int index, V128 value)
{
_setSimdFpReg(_vcpu, HvSimdFPReg.Q0 + (uint)index, value, _setSimdFpRegNativePtr).ThrowOnError();
lock (_registerLock)
{
if ((uint)index > 31) return;
HvResult res = _setSimdFpReg(_vcpu, HvSimdFPReg.Q0 + (uint)index, value, _setSimdFpRegNativePtr);
if (res == HvResult.BadArgument)
{
Interlocked.Increment(ref _fallbackCount);
LogHvWarning("SetV", $"Q{index}");
_v[index] = value;
return;
}
res.ThrowOnError();
_v[index] = value;
}
}
private ulong GetRegCached(HvReg reg, ref ulong cached, string name)
{
HvResult res = HvApi.hv_vcpu_get_reg(_vcpu, reg, out ulong val);
if (res == HvResult.BadArgument)
{
Interlocked.Increment(ref _fallbackCount);
LogHvWarning("GetReg", name);
return cached;
}
res.ThrowOnError();
return cached = val;
}
private void SetRegCached(HvReg reg, ulong value, ref ulong cached, string name)
{
HvResult res = HvApi.hv_vcpu_set_reg(_vcpu, reg, value);
if (res == HvResult.BadArgument)
{
Interlocked.Increment(ref _fallbackCount);
LogHvWarning("SetReg", name, $"value=0x{value:X16}");
cached = value;
return;
}
res.ThrowOnError();
cached = value;
}
private ulong GetSysRegCached(HvSysReg reg, ref ulong cached, string name)
{
HvResult res = HvApi.hv_vcpu_get_sys_reg(_vcpu, reg, out ulong val);
if (res == HvResult.BadArgument)
{
Interlocked.Increment(ref _fallbackCount);
LogHvWarning("GetSysReg", name);
return cached;
}
res.ThrowOnError();
return cached = val;
}
private void SetSysRegCached(HvSysReg reg, ulong value, ref ulong cached, string name)
{
HvResult res = HvApi.hv_vcpu_set_sys_reg(_vcpu, reg, value);
if (res == HvResult.BadArgument)
{
Interlocked.Increment(ref _fallbackCount);
LogHvWarning("SetSysReg", name, $"value=0x{value:X16}");
cached = value;
return;
}
res.ThrowOnError();
cached = value;
}
public long GetFallbackCount() => Interlocked.Read(ref _fallbackCount);
public void RequestInterrupt()
{
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0)
@@ -45,7 +45,7 @@ namespace Ryujinx.Cpu.AppleHv
public HvMemoryBlockAllocation Allocate(ulong size, ulong alignment)
{
var allocation = Allocate(size, alignment, CreateBlock);
Allocation allocation = Allocate(size, alignment, CreateBlock);
return new HvMemoryBlockAllocation(this, allocation.Block, allocation.Offset, allocation.Size);
}
+4 -4
View File
@@ -237,17 +237,17 @@ namespace Ryujinx.Cpu.AppleHv
return [];
}
var guestRegions = GetPhysicalRegionsImpl(va, size);
List<MemoryRange> guestRegions = GetPhysicalRegionsImpl(va, size);
if (guestRegions == null)
{
return null;
}
var regions = new HostMemoryRange[guestRegions.Count];
HostMemoryRange[] regions = new HostMemoryRange[guestRegions.Count];
for (int i = 0; i < regions.Length; i++)
{
var guestRegion = guestRegions[i];
MemoryRange guestRegion = guestRegions[i];
nint pointer = _backingMemory.GetPointer(guestRegion.Address, guestRegion.Size);
regions[i] = new HostMemoryRange((nuint)(ulong)pointer, guestRegion.Size);
}
@@ -275,7 +275,7 @@ namespace Ryujinx.Cpu.AppleHv
int pages = GetPagesCount(va, (uint)size, out va);
var regions = new List<MemoryRange>();
List<MemoryRange> regions = new();
ulong regionStart = GetPhysicalAddressInternal(va);
ulong regionSize = PageSize;
+1 -1
View File
@@ -41,7 +41,7 @@ namespace Ryujinx.Cpu.AppleHv
{
// Calculate our time delta in ticks based on the current clock frequency.
int result = TimeApi.mach_timebase_info(out var timeBaseInfo);
int result = TimeApi.mach_timebase_info(out MachTimebaseInfo timeBaseInfo);
Debug.Assert(result == 0);
+1 -1
View File
@@ -38,7 +38,7 @@ namespace Ryujinx.Cpu.AppleHv
baseAddress = ipaAllocator.Allocate(block.Size, AsIpaAlignment);
}
var rwx = HvMemoryFlags.Read | HvMemoryFlags.Write | HvMemoryFlags.Exec;
HvMemoryFlags rwx = HvMemoryFlags.Read | HvMemoryFlags.Write | HvMemoryFlags.Exec;
HvApi.hv_vm_map((ulong)block.Pointer, baseAddress, block.Size, rwx).ThrowOnError();
+3 -3
View File
@@ -1,4 +1,5 @@
using System;
using ARMeilleure.Translation.PTC;
namespace Ryujinx.Cpu
{
@@ -44,11 +45,10 @@ namespace Ryujinx.Cpu
/// <remarks>
/// If the execution engine is recompiling guest code, this can be used to load cached code from disk.
/// </remarks>
/// <param name="titleIdText">Title ID of the application in padded hex form</param>
/// <param name="displayVersion">Version of the application</param>
/// <param name="cacheInfo">Identity and selector for the process-owned disk cache</param>
/// <param name="enabled">True if the cache should be loaded from disk if it exists, false otherwise</param>
/// <returns>Disk cache load progress reporter and manager</returns>
IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector);
IDiskCacheLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled);
/// <summary>
/// Indicates that code has been loaded into guest memory, and that it might be executed in the future.
@@ -127,7 +127,7 @@ namespace Ryujinx.Cpu.Jit.HostTracked
Debug.Assert(leftSize > 0);
Debug.Assert(rightSize > 0);
(var leftAllocation, PrivateAllocation) = PrivateAllocation.Split(leftSize);
(PrivateMemoryAllocation leftAllocation, PrivateAllocation) = PrivateAllocation.Split(leftSize);
PrivateMapping left = new(Address, leftSize, leftAllocation);
+9 -6
View File
@@ -1,6 +1,7 @@
using ARMeilleure.Common;
using ARMeilleure.Memory;
using ARMeilleure.Translation;
using ARMeilleure.Translation.PTC;
using Ryujinx.Cpu.Signal;
namespace Ryujinx.Cpu.Jit
@@ -9,13 +10,15 @@ namespace Ryujinx.Cpu.Jit
{
private readonly ITickSource _tickSource;
private readonly Translator _translator;
private readonly AddressTable<ulong> _functionTable;
public JitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
{
_tickSource = tickSource;
_functionTable = AddressTable<ulong>.CreateForArm(for64Bit, memory.Type);
_translator = new Translator(new JitMemoryAllocator(forJit: true), memory, _functionTable);
bool sparse = memory.Type is not MemoryManagerType.SoftwareMmu and not MemoryManagerType.SoftwarePageTable;
IAddressTable<ulong> functionTable = sparse ? SparseAddressTable<ulong>.CreateForArm(for64Bit) : AddressTable<ulong>.CreateForArm(for64Bit);
_translator = new Translator(new JitMemoryAllocator(forJit: true), memory, functionTable);
if (memory.Type.IsHostMappedOrTracked())
{
@@ -49,15 +52,15 @@ namespace Ryujinx.Cpu.Jit
}
/// <inheritdoc/>
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
public IDiskCacheLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled)
{
return new JitDiskCacheLoadState(_translator.LoadDiskCache(titleIdText, displayVersion, enabled, cacheSelector));
return new JitDiskCacheLoadState(_translator.LoadDiskCache(cacheInfo, enabled));
}
/// <inheritdoc/>
public void PrepareCodeRange(ulong address, ulong size)
{
_functionTable.SignalCodeRange(address, size);
_translator.FunctionTable.SignalCodeRange(address, size);
_translator.PrepareCodeRange(address, size);
}
+4 -4
View File
@@ -257,17 +257,17 @@ namespace Ryujinx.Cpu.Jit
return [];
}
var guestRegions = GetPhysicalRegionsImpl(va, size);
List<MemoryRange> guestRegions = GetPhysicalRegionsImpl(va, size);
if (guestRegions == null)
{
return null;
}
var regions = new HostMemoryRange[guestRegions.Count];
HostMemoryRange[] regions = new HostMemoryRange[guestRegions.Count];
for (int i = 0; i < regions.Length; i++)
{
var guestRegion = guestRegions[i];
MemoryRange guestRegion = guestRegions[i];
nint pointer = _backingMemory.GetPointer(guestRegion.Address, guestRegion.Size);
regions[i] = new HostMemoryRange((nuint)(ulong)pointer, guestRegion.Size);
}
@@ -295,7 +295,7 @@ namespace Ryujinx.Cpu.Jit
int pages = GetPagesCount(va, (uint)size, out va);
var regions = new List<MemoryRange>();
List<MemoryRange> regions = new();
ulong regionStart = GetPhysicalAddressInternal(va);
ulong regionSize = PageSize;
@@ -345,7 +345,7 @@ namespace Ryujinx.Cpu.Jit
{
int pages = GetPagesCount(va, (uint)size, out va);
var regions = new List<MemoryRange>();
List<MemoryRange> regions = new();
ulong regionStart = GetPhysicalAddressChecked(va);
ulong regionSize = PageSize;
@@ -249,7 +249,7 @@ namespace Ryujinx.Cpu.Jit
if (TryGetVirtualContiguous(va, data.Length, out MemoryBlock memoryBlock, out ulong offset))
{
var target = memoryBlock.GetSpan(offset, data.Length);
Span<byte> target = memoryBlock.GetSpan(offset, data.Length);
bool changed = !data.SequenceEqual(target);
@@ -448,7 +448,7 @@ namespace Ryujinx.Cpu.Jit
return null;
}
var regions = new List<HostMemoryRange>();
List<HostMemoryRange> regions = new();
ulong endVa = va + size;
try
@@ -489,7 +489,7 @@ namespace Ryujinx.Cpu.Jit
int pages = GetPagesCount(va, (uint)size, out va);
var regions = new List<MemoryRange>();
List<MemoryRange> regions = new();
ulong regionStart = GetPhysicalAddressInternal(va);
ulong regionSize = PageSize;
@@ -13,7 +13,7 @@ namespace Ryujinx.Cpu.LightningJit
CpuPreset cpuPreset,
IMemoryManager memoryManager,
ulong address,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
nint dispatchStubPtr,
ExecutionMode executionMode,
Architecture targetArch)
@@ -12,7 +12,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
CpuPreset cpuPreset,
IMemoryManager memoryManager,
ulong address,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
nint dispatchStubPtr,
bool isThumb,
Architecture targetArch)
@@ -8,7 +8,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
{
static class Decoder<T> where T : IInstEmit
{
public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, bool isThumb)
public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, bool singleBlock, bool isThumb)
{
List<Block> blocks = [];
List<ulong> branchTargets = [];
@@ -24,7 +24,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
blocks.Add(block);
if (block.IsTruncated || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets))
if (block.IsTruncated || (singleBlock && block.EndsWithBranch) || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets))
{
break;
}
@@ -22,7 +22,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
public readonly RegisterAllocator RegisterAllocator;
public readonly MemoryManagerType MemoryManagerType;
public readonly TailMerger TailMerger;
public readonly AddressTable<ulong> FuncTable;
public readonly IAddressTable<ulong> FuncTable;
public readonly nint DispatchStubPointer;
private readonly RegisterSaveRestore _registerSaveRestore;
@@ -33,7 +33,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
RegisterAllocator registerAllocator,
MemoryManagerType mmType,
TailMerger tailMerger,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
RegisterSaveRestore registerSaveRestore,
nint dispatchStubPointer,
nint pageTablePointer)
@@ -225,9 +225,9 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
}
}
public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, AddressTable<ulong> funcTable, nint dispatchStubPtr, bool isThumb)
public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, IAddressTable<ulong> funcTable, nint dispatchStubPtr, bool isThumb)
{
MultiBlock multiBlock = Decoder<InstEmit>.DecodeMulti(cpuPreset, memoryManager, address, isThumb);
MultiBlock multiBlock = Decoder<InstEmit>.DecodeMulti(cpuPreset, memoryManager, address, singleBlock: true, isThumb);
Dictionary<ulong, int> targets = new();
@@ -3,6 +3,7 @@ using Ryujinx.Cpu.LightningJit.CodeGen;
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
using System;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -132,7 +133,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
RegisterAllocator regAlloc,
TailMerger tailMerger,
Action writeEpilogue,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
nint funcPtr,
int spillBaseOffset,
uint nextAddress,
@@ -143,7 +144,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
int tempGuestAddress = -1;
bool inlineLookup = guestAddress.Kind != OperandKind.Constant &&
funcTable is { Sparse: true };
funcTable.TableType == AddressTableType.Sparse;
if (guestAddress.Kind == OperandKind.Constant)
{
@@ -189,7 +190,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
}
else if (inlineLookup)
{
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
// Inline table lookup. Only enabled when the sparse function table is enabled with 1 level.
Operand indexReg = Register(NextFreeRegister(tempRegister + 1, tempGuestAddress));
@@ -203,18 +204,15 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
// Index into the table.
asm.Mov(rn, tableBase);
for (int i = 0; i < funcTable.Levels.Length; i++)
{
var level = funcTable.Levels[i];
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
asm.Lsl(indexReg, indexReg, Const(3));
AddressTableLevel level = funcTable.Levels.Last();
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
asm.Lsl(indexReg, indexReg, Const(3));
// Index into the page.
asm.Add(rn, rn, indexReg);
// Index into the page.
asm.Add(rn, rn, indexReg);
// Load the page address.
asm.LdrRiUn(rn, rn, 0);
}
// Load the page address.
asm.LdrRiUn(rn, rn, 0);
if (tempGuestAddress != -1)
{
@@ -12,7 +12,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
CpuPreset cpuPreset,
IMemoryManager memoryManager,
ulong address,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
nint dispatchStubPtr,
Architecture targetArch)
{
@@ -18,7 +18,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
public readonly CodeWriter Writer;
public readonly RegisterAllocator RegisterAllocator;
public readonly TailMerger TailMerger;
public readonly AddressTable<ulong> FuncTable;
public readonly IAddressTable<ulong> FuncTable;
public readonly nint DispatchStubPointer;
private readonly MultiBlock _multiBlock;
@@ -31,7 +31,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
TailMerger tailMerger,
RegisterSaveRestore registerSaveRestore,
MultiBlock multiBlock,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
nint dispatchStubPointer,
nint pageTablePointer)
{
@@ -303,9 +303,9 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
}
}
public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, AddressTable<ulong> funcTable, nint dispatchStubPtr)
public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, IAddressTable<ulong> funcTable, nint dispatchStubPtr)
{
MultiBlock multiBlock = Decoder.DecodeMulti(cpuPreset, memoryManager, address);
MultiBlock multiBlock = Decoder.DecodeMulti(cpuPreset, memoryManager, address, singleBlock: true);
Dictionary<ulong, int> targets = new();
List<PendingBranch> pendingBranches = [];
@@ -13,7 +13,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
private const uint NzcvFlags = 0xfu << 28;
private const uint CFlag = 0x1u << 29;
public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address)
public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, bool singleBlock)
{
List<Block> blocks = [];
List<ulong> branchTargets = [];
@@ -35,7 +35,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
blocks.Add(block);
if (block.IsTruncated || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets))
if (block.IsTruncated || (singleBlock && block.EndsWithBranch) || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets))
{
break;
}
@@ -3,6 +3,7 @@ using Ryujinx.Cpu.LightningJit.CodeGen;
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
using System;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -214,7 +215,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
RegisterAllocator regAlloc,
TailMerger tailMerger,
Action writeEpilogue,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
nint dispatchStubPtr,
InstName name,
ulong pc,
@@ -297,7 +298,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
RegisterAllocator regAlloc,
TailMerger tailMerger,
Action writeEpilogue,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
nint funcPtr,
int spillBaseOffset,
ulong pc,
@@ -308,7 +309,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
int tempGuestAddress = -1;
bool inlineLookup = guestAddress.Kind != OperandKind.Constant &&
funcTable is { Sparse: true };
funcTable.TableType == AddressTableType.Sparse;
if (guestAddress.Kind == OperandKind.Constant)
{
@@ -354,7 +355,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
}
else if (inlineLookup)
{
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
// Inline table lookup. Only enabled when the sparse function table is enabled with 1 level.
Operand indexReg = Register(NextFreeRegister(tempRegister + 1, tempGuestAddress));
@@ -368,18 +369,15 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
// Index into the table.
asm.Mov(rn, tableBase);
for (int i = 0; i < funcTable.Levels.Length; i++)
{
var level = funcTable.Levels[i];
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
asm.Lsl(indexReg, indexReg, Const(3));
AddressTableLevel level = funcTable.Levels.Last();
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
asm.Lsl(indexReg, indexReg, Const(3));
// Index into the page.
asm.Add(rn, rn, indexReg);
// Index into the page.
asm.Add(rn, rn, indexReg);
// Load the page address.
asm.LdrRiUn(rn, rn, 0);
}
// Load the page address.
asm.LdrRiUn(rn, rn, 0);
if (tempGuestAddress != -1)
{
@@ -24,11 +24,30 @@ namespace Ryujinx.Cpu.LightningJit.Cache
}
}
private readonly int _regionSize;
private int _regionCount;
private readonly List<MemoryBlock> _blocks = [];
public CacheMemoryAllocator(int capacity)
public CacheMemoryAllocator(int regionSize, int initialRegionCount = 1)
{
_blocks.Add(new MemoryBlock(0, capacity));
_regionCount = 0;
_regionSize = regionSize;
for (; initialRegionCount > 0; initialRegionCount--)
{
_blocks.Add(new MemoryBlock(_regionSize * _regionCount, _regionSize));
_regionCount++;
}
}
public void AddNewBlocks(int count)
{
for (; count > 0; count--)
{
_blocks.Add(new MemoryBlock(_regionSize * _regionCount, _regionSize));
_regionCount++;
}
}
public int Allocate(int size)
@@ -101,12 +120,13 @@ namespace Ryujinx.Cpu.LightningJit.Cache
index = ~index;
}
if (index < _blocks.Count)
int endOffs = block.Offset + block.Size;
// Don't merge blocks from different allocations
if (index < _blocks.Count && endOffs % _regionSize != 0)
{
MemoryBlock next = _blocks[index];
int endOffs = block.Offset + block.Size;
if (next.Offset == endOffs)
{
block = new MemoryBlock(block.Offset, block.Size + next.Size);
@@ -114,7 +134,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
}
}
if (index > 0)
// Don't merge blocks from different allocations
if (index > 0 && block.Offset % _regionSize != 0)
{
MemoryBlock prev = _blocks[index - 1];
+71 -96
View File
@@ -4,83 +4,50 @@ using Ryujinx.Common.Logging;
using Ryujinx.Memory;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Cpu.LightningJit.Cache
{
static partial class JitCache
partial class JitCache : IDisposable
{
private static readonly int _pageSize = (int)MemoryBlock.GetPageSize();
private static readonly int _pageMask = _pageSize - 1;
private const int CodeAlignment = 4; // Bytes.
// TODO: JIT Cache size should be application dependent, not global.
private const int CacheSize = 1024 * (1024 * 1024); // Megabytes * Size of Megabytes (since its in bytes).
private const uint CacheSize = 256 * 1024 * 1024; // Megabytes * Size of Megabytes (since its in bytes).
private static JitCacheInvalidation _jitCacheInvalidator;
private readonly JitCacheInvalidation _jitCacheInvalidator;
private static CacheMemoryAllocator _cacheAllocator;
private readonly CacheMemoryAllocator _cacheAllocator;
private static readonly List<CacheEntry> _cacheEntries = [];
private readonly List<CacheEntry> _cacheEntries = [];
private static readonly Lock _lock = new();
private static bool _initialized;
private static readonly List<ReservedRegion> _jitRegions = [];
private static int _activeRegionIndex = 0;
private readonly Lock _lock = new();
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
private readonly List<ReservedRegion> _jitRegions = [];
[SupportedOSPlatform("macos")]
[LibraryImport("libSystem.dylib", EntryPoint = "sys_icache_invalidate")]
internal static partial void SysICacheInvalidate(nint start, nuint len);
[SupportedOSPlatform("linux")]
[LibraryImport("libgcc_s.so.1", EntryPoint = "__clear_cache")]
internal static partial void ClearCache(nint begin, nint end);
public static void Initialize(IJitMemoryAllocator allocator)
public JitCache(IJitMemoryAllocator allocator)
{
if (_initialized)
{
return;
}
lock (_lock)
{
if (_initialized)
{
return;
}
var firstRegion = new ReservedRegion(allocator, CacheSize);
_jitRegions.Add(firstRegion);
_activeRegionIndex = 0;
_jitRegions.Add(new(allocator, CacheSize));
_cacheAllocator = new CacheMemoryAllocator((int)CacheSize);
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
}
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
_initialized = true;
}
}
public unsafe static nint Map(ReadOnlySpan<byte> code)
public unsafe nint Map(ReadOnlySpan<byte> code)
{
lock (_lock)
{
Debug.Assert(_initialized);
int funcOffset = Allocate(code.Length);
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
nint funcPtr = targetRegion.Pointer + funcOffset;
nint funcPtr = GetFunctionPtr(funcOffset);
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
@@ -94,18 +61,11 @@ namespace Ryujinx.Cpu.LightningJit.Cache
}
else
{
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
ReprotectAsWritable(funcOffset, code.Length);
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
ReprotectAsExecutable(funcOffset, code.Length);
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (nuint)code.Length);
}
else
{
_jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
}
_jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
}
Add(funcOffset, code.Length);
@@ -114,21 +74,20 @@ namespace Ryujinx.Cpu.LightningJit.Cache
}
}
public static void Unmap(nint pointer)
public void Unmap(nint pointer)
{
lock (_lock)
{
Debug.Assert(_initialized);
foreach (var region in _jitRegions)
for (int i = 0; i < _jitRegions.Count; i++)
{
ReservedRegion region = _jitRegions[i];
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
pointer.ToInt64() >= (region.Pointer + (nint)CacheSize).ToInt64())
{
continue;
}
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64() + i * CacheSize);
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{
@@ -141,59 +100,67 @@ namespace Ryujinx.Cpu.LightningJit.Cache
}
}
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
private void ReprotectAsWritable(int offset, int size)
{
int endOffs = offset + size;
int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
int regionStart = (offset % (int)CacheSize) & ~_pageMask;
int regionEnd = endOffs % (int)CacheSize == 0
? (((int)CacheSize) + _pageMask) & ~_pageMask
: ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
GetRegion(offset).Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
private void ReprotectAsExecutable(int offset, int size)
{
int endOffs = offset + size;
int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
int regionStart = (offset % (int)CacheSize) & ~_pageMask;
int regionEnd = endOffs % (int)CacheSize == 0
? (((int)CacheSize) + _pageMask) & ~_pageMask
: ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
GetRegion(offset).Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}
private static int Allocate(int codeSize)
private int Allocate(int codeSize)
{
codeSize = AlignCodeSize(codeSize);
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
{
int allocOffset = _cacheAllocator.Allocate(codeSize);
int allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset >= 0)
{
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
_activeRegionIndex = i;
return allocOffset;
}
if (allocOffset >= 0)
{
GetRegion(allocOffset).ExpandIfNeeded((ulong)(allocOffset % (int)CacheSize) + (ulong)codeSize);
return allocOffset;
}
int exhaustedRegion = _activeRegionIndex;
var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
_cacheAllocator.AddNewBlocks(1);
ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache of size {(_jitRegions.Count * CacheSize).Bytes()} exhausted, creating new Cache Region ({((_jitRegions.Count + 1) * CacheSize).Bytes()} Total Allocation).");
_jitRegions.Add(newRegion);
_activeRegionIndex = _jitRegions.Count - 1;
int newRegionNumber = _activeRegionIndex;
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((long)(newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
if (allocOffsetNew < 0)
allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset < 0)
{
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
}
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
return allocOffsetNew;
GetRegion(allocOffset).ExpandIfNeeded((ulong)(allocOffset % (int)CacheSize) + (ulong)codeSize);
return allocOffset;
}
private nint GetFunctionPtr(int offset)
{
return GetRegion(offset).Pointer + (offset % (int)CacheSize);
}
private ReservedRegion GetRegion(int offset)
{
int index = offset / (int)CacheSize;
return _jitRegions[index];
}
private static int AlignCodeSize(int codeSize)
@@ -201,7 +168,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
}
private static void Add(int offset, int size)
private void Add(int offset, int size)
{
CacheEntry entry = new(offset, size);
@@ -215,7 +182,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
_cacheEntries.Insert(index, entry);
}
public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
public bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
{
lock (_lock)
{
@@ -238,5 +205,13 @@ namespace Ryujinx.Cpu.LightningJit.Cache
entryIndex = 0;
return false;
}
public void Dispose()
{
foreach (ReservedRegion jitRegion in _jitRegions)
{
jitRegion.Dispose();
}
}
}
}
@@ -190,7 +190,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
private bool TryGetThreadLocalFunction(ulong guestAddress, out nint funcPtr)
{
if ((_threadLocalCache ??= new()).TryGetValue(guestAddress, out var entry))
if ((_threadLocalCache ??= new()).TryGetValue(guestAddress, out ThreadLocalCacheEntry entry))
{
if (entry.IncrementUseCount() >= MinCallsForPad)
{

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