Repository navigation
Conversation
The GuestSignal_* stubs now branch to an optional frontend handler (Pointers.GuestSignalHandler) after spilling the static registers, instead of trapping. ARM64EC sets it to HandleGuestSignal, which raises the guest exception directly rather than through a host signal, its dispatch in ntdll and RethrowGuestException. The shared part of RethrowGuestException moves into RaiseGuestException.
Member
|
Good shout, I was wondering if double faulting was going to cause a performance problem in the future, but I didn't have a real game that abused it to know. This isn't up to our regular standards so I'm going to implement this myself and also hammer it out for wow64 and Linux and make a microbench for it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When JIT code raises a guest exception (ud2, int3, int 0x2d or 0x29, a trap-flag step, a privileged instruction or an execute fault), it fills in SynchronousFaultData and branches to one of the GuestSignal stubs in the dispatcher. The stub spills the static registers and then traps on the host with hlt, brk or a null load. On ARM64EC that trap goes through Wine's signal handler and a first pass through KiUserExceptionDispatcher, and all that pass does is call
RethrowGuestException, which rebuilds the guest context from the spilled state and raises it again with NtRaiseException.This adds an optional
Pointers.GuestSignalHandler. When it's set, the stubs branch to it after spilling instead of trapping. ARM64EC points it atHandleGuestSignal, which builds the guest context from the spilled state and raises the exception the same wayRethrowGuestExceptiondoes. The part both of them share is nowRaiseGuestException.HandleGuestSignal passes along the exception code the host trap would have produced: illegal instruction for hlt, breakpoint for brk and access violation for the null load. HandleGuestException keeps that code for execute faults, so it matters there. Frontends that don't set the pointer (Linux and WOW64) only get one extra load and branch in the stubs.
I tested it on an AYN Thor (Snapdragon 8 Gen 2) with GameNative and Proton 11 ARM64EC. The test is a small x64 program that records what a vectored exception handler sees for ud2, int3, a trap-flag step, hlt and a call into a non-executable page, and then times 20,000 ud2 and 20,000 int3 round trips.
On main (dab12ae), the records are the same with and without this change: code, flags, parameters, address and context registers. Each exception takes about 100 µs either way, though, because with this Wine NtContinue makes a wineserver round trip and that is most of the cost.
The FEX build I use for Age of Empires IV is based on 7d3090f and has a patch that skips that round trip, which Wine master no longer makes. With this change on top, the records still match, and an exception takes 0.86 µs instead of 1.82 µs for ud2 and 0.86 µs instead of 1.69 µs for int3. That game's anti-tamper raises around 44,000 illegal-instruction exceptions per second, and an earlier version of this change took its skirmish benchmark from 56.4 to 57.6 FPS.
Jumping straight into KiUserExceptionDispatcher instead of going through NtRaiseException would save a bit more, but then a debugger wouldn't see these first-chance exceptions, so I left that out.