WIP: perf(lift): fast float regions, plain SSE checked once by MXCSR with the exact copy as fallback #38
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
needs measurements
question
testing wanted
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!38
Loading…
Reference in a new issue
No description provided.
Delete branch "dyst/Longshot:perf/hot-paths"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
The frame is bound by the game's own job graph, and its biggest serial steps (Havok AI's character step, Link's movement and collision) are mostly float math that the lifted code runs through an exactness check on every single op, about 25 instructions around each SSE op. This lifts straight runs of that math twice: a plain-SSE copy checked once, and the exact copy kept as the fallback.
What changed
ff_eligibleandff_regioninthird_party/a64-kit/lift/a64_lift.py: a straight run of instructions with no label, store, call, branch or system-register access, holding at least 8 (A64_FASTFP_MIN) single-precision adds, subtracts, multiplies, fused multiply-adds, divides or square roots, is emitted twice: a fast copy, the ops as plain SSE (asm volatile, in Arm's operand order; multiply-adds asvfmadd231sson FMA3 builds) between one MXCSR flag clear and one read (a64_ff_begin,a64_ff_end); and the exact copy, the lifted code as before. On an invalid, denormal or underflow flag the registers the run names are put back and the exact copy runs. Divide-by-zero, overflow and inexact are or-ed into FPSR.Lifter's function emitter gathers joinable instructions into a run (flush), countsff_regionsandff_ops, and prints afast float regions (A64_FASTFP=...)line;FASTFP_HEADERis written beside the fpx header when the mode is on.FASTFP_HEADER:A64_FF_ONgates on the existingRECOMP_FPX_OPEN(FPCR at its defaults apart from FZ, FPSR.IXC set) and ona64_ff_enabled_(HOS_NO_FASTFP).a64_ff_guard_raises the invalid flag on purpose for results x86 gets right in value but not in Arm's flags, or not in value under FZ: any NaN result; a product, quotient or multiply-add of exactly 2^-126; a subnormal divided by a zero. The 2-lane vector ops load their upper lanes as zeros. Without FMA3 a multiply-add calls the exact helper inside the fast copy; non-x86 builds compile the exact copy only.fastfp_mode:A64_FASTFP=0off;hottheA64_HOT_FUNCSlist only (ff_only);checkboth copies every time, compared (a64_ff_check,fastfp check:lines in the log with counts per chunk and any mismatch's guest address).tests/test_fastfp_fma.pywithtests/fastfp_fma_test.c(new, @Jiro131's, extended) andtests/test_fastfp_regions.py(new).Flags and settings
HOS_NO_FASTFPA64_FASTFP0off;hotthe hot functions only;checkboth copies every time, compared, withfastfp check:lines in the log1, every function)A64_FASTFP_MINMeasurements
Measured on the GitHub version (NotRexed/Hookshot#2433), not yet on Longshot: this PR stays WIP until it is.
Measured on
ebbba2ea, before the rebase and before the result checks of 1e6b7a2f, which add a compare per op.Hardware: Ryzen 9 3900X, Windows
Scene: Temple of Time; Hyrule Town; Hyrule Castle
Method: alternating runs in the same session (this PC's run-to-run noise is about ±8%); Hyrule Town and Hyrule Castle two alternating pairs each, base against this PR
ebbba2eaTesting
Tested: the runs under Measurements (on the GitHub version) and the game-free checks this description names. Checked but not measured: check mode in eight saves (above), for correctness rather than speed.
Not tested: Intel, Linux and CPUs without AVX2 (the SSE path has run only in the unit test so far). Reports from them are welcome;
HOS_NO_FASTFPturns it off on the same exe to compare.Needs other testers:
HOS_NO_FASTFP, expecting the same picture, no crash, and its fps (request to open)HOS_NO_FASTFP(request to open)Screenshots
Nothing on screen.
Risk and rollback
HOS_NO_FASTFPturns the fast copies off at run time on the same exe;A64_FASTFP=0lifts without them; reverting the commit is enough.Reviewer notes
Carried over from NotRexed/Hookshot#2433 with the move to Longshot: the same change on Longshot's
main, with its rename applied. Its review and discussion are there (reviewed by Joey-Magnus).third_party/a64-kit/lift/a64_lift.py(the lifter), so thethird_party/box is not ticked.tests/test_fastfp_fma.pyand by the same test once the divide was added to it; check mode never met them in game. Rebased onto GitHub's main, including NotRexed/Hookshot#2383 (the exact path's single-precision FMA fused on x86-64-v3): the exact copies get it, the fast copies use their ownvfmadd231ss.tests/test_psychov35.py, from NotRexed/Hookshot#2481 on main (scanfis deprecated under-Werrorwith the runner's MSVC-target clang), not from this PR.tests/test_fastfp_fma.py: each fast op alone in the MXCSR window against the exact body the lifted code falls back to (recomp_fpe_fma32,_div32,_add32,_sub32; the vector ops lane by lane, 4 lanes and 2), value and FPSR, FZ off and on, with random operands weighted to the underflow and overflow edges, NaNs, subnormals and cancellations, plus an edge list: 0 mismatches in 2–8M checks per op. It fails if any of the result checks is removed, or the 2-lane ops load four lanes.tests/test_fastfp_regions.py: what may join a region, and a whole region from the real header against plain IEEE results at-march=x86-64-v3andx86-64-v2(200,000 random runs; a multiply-add rounded once; NaN results, 0 * inf, a denormal operand and a tiny result under FZ take the exact copy with the inputs put back; overflow and divide-by-zero stay fast with FPSR set; the gate closed orHOS_NO_FASTFPtake the exact copy). It fails if the AVX operands are swapped or the invalid flag stops sending a region to its exact copy. It links libm only off Windows.mainis worth a report); or build withA64_FASTFP=check, play anywhere, and look inlongshot_log.txtforfastfp check: MISMATCH(there should be none); or compareHOS_THREAD_TIMES=5with and withoutHOS_NO_FASTFPon the same exe.third_party/: this diff changes files underthird_party/, which Longshot's rules keep as unmodified upstream copies (a new folder with aPROVENANCE.mdinstead). That has to be resolved before this can be ready.Issues
None.
Checklist
For everyone
type(scope): description).main.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above).third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.docs/tested-configurations.mdfor each measured run.MKWVK_API_VERSIONbumped (gpu/vk/vk_api.h'sMkwVkHostorMkwVkRendererchanged).mainand passing here.tests/that reads changed source updated in this PR, not weakened.🤖 Generated with Claude Code
perf(lift): fast float regions, plain SSE checked once by MXCSR with the exact copy as fallbackto WIP: perf(lift): fast float regions, plain SSE checked once by MXCSR with the exact copy as fallbackBack to WIP under #48's process: this was carried over before the new template. Its description will be brought to the template and measured on Longshot (main against this PR, at named commits) when it's wanted for review; ask here if you'd like it sooner.
Description brought to Longshot's PR template (#48). Still WIP: its numbers are from the GitHub version, and it isn't rebased on current main; it gets measured on Longshot (main against this PR, at named commits) before it is marked ready. Note: this diff changes files under third_party/, which Longshot keeps as unmodified upstream copies; that needs resolving before it can be ready.
Description moved to the template's Testing section and whole checklist (#72): what was tested, what wasn't, and what needs other testers (their test requests to open while this is WIP).
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.