Skip to content

perf(shader): choose the fma NaN result only when the result is NaN - #2279

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
karanvora2599:perf/shader-nan-result-guard
Oct 10, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
karanvora2599:perf/shader-nan-result-guard

Conversation

@karanvora2599

Copy link
Copy Markdown
Contributor

What

Follow-up to #2083. nanResultF32 chooses the NaN result of v_add/sub/mul_f32 and of the f32 fma family (v_fma/fmac/fmamk/fmaak_f32, v_fma_mix_f32 with f32 sources) on the bits: the first NaN source, quieted in IEEE mode, and 0xffc00000 when no source is a NaN; for the fma family the addend is skipped when the product is infinity times zero. That selection and the infinity-times-zero test run for every op, but a NaN source always gives a NaN result, so for any result that isn't a NaN they return the result unchanged.

For the fma family:

  • New FPNanResultFma32(result, lhs, rhs, addend), with the IEEE mode as its flag. nanResultF32 emits it for three sources, so invalidProductF32 moves into the emitter.
  • The SPIR-V emitter tests the result's bits and makes the selection, including the infinity-times-zero test, in a selection taken only when the result is a NaN; otherwise the result passes through. Inside it the rule is feat(shader): choose f32 arithmetic NaN results on the bits #2083's, unchanged.
  • The SRT evaluator computes the op exactly; MaskedSelectEliminator treats it as lane-local like FPFma32.

Add, sub and mul keep #2083's inline selects. With two sources the selection is cheap, and the branch costs more than it saves on NVIDIA: with the same branch for all four, a dependent chain of 1024 v_add_f32 went from 8.87 to 12.09 ms (best of 4 alternated runs) and v_mul_f32 showed no clear gain, while v_fma_f32 went from 25.5 to 19.1 ms.

Results are bit-identical.

Tested

Windows 11, MinGW-w64 GCC 15.2.0 (WinLibs r7), Release, ANYPS5_ENABLE_SPIRV_TOOLS=ON, main at d70b899. GPU tests on an NVIDIA GeForce RTX 4060 Laptop GPU (591.86, subgroup 32) and on lavapipe (Mesa 26.2.4 for Windows, GALLIUM_OVERRIDE_CPU_CAPS=avx).

  • agc_driver_f32_arithmetic_nan (feat(shader): choose f32 arithmetic NaN results on the bits #2083's rows measured on hardware, with signaling and quiet payloads, infinity times zero with a NaN addend, IEEE=0 and IEEE=1) passes on both devices, as do agc_driver_f32_denormal_flush, agc_driver_f32_output_modifier, agc_driver_f32_min_max_nan, vector_fused_multiply_add, vector_mad_legacy, agc_shader_memory and agc_masked_select.

  • New vector_fma_nan_result: v_fma_f32 and v_fmac_f32 emit one FPNanResultFma32 with the IEEE flag of the float mode, v_add_f32 none; the SRT evaluator keeps a number result, picks the first NaN source, quiets it in IEEE mode, skips the NaN addend of infinity times zero and keeps it for infinity times infinity.

  • Negative checks, each reverted:

    • the infinity-times-zero test dropped from the emitter: agc_driver_f32_arithmetic_nan fails on both devices (lane 10 no float mode v_fma_f32 is 0x7fc00003, expected 0xffc00000).
    • the IEEE flag ignored by the emitter: it fails on both devices (lane 0 IEEE=1 v_fma_f32 is 0x7f812345, expected 0x7fc12345).
    • the infinity-times-zero test dropped from the SRT evaluator: vector_fma_nan_result fails (a NaN addend of an invalid product is not ignored).
  • Full ctest after cmake --build build and --target libs, serial, --timeout 120, RTX 4060: 490 pass, 2 skip (agc_driver_storage_cache_budget, guest_video_out_wqhd_detection_no_vulkan), 19 fail. The 19 are relinker tests: during the run Windows Defender quarantined the freshly built build/core/relinker/relinker.exe (and, as on perf(shader): skip the tiny product check that a constant factor settles #2020, an executable tls_function_coverage writes), so every relinker test after it failed to start it ([WinError 225] ... contains a virus or potentially unwanted software). The relinker's build contains no recompiler sources.

  • Per op in FLOAT_MODE 0xc0 on the RTX 4060 (pipeline statistics of a compute program with a dependent chain of 8 ops, not committed): v_fma_f32 goes from 38 to 41 instructions and from 33 to 30 registers, since the selection stays in the branch; v_add_f32 (14) and v_mul_f32 (30) are unchanged. Without any NaN selection v_fma_f32 would be 25.

  • Throughput on the RTX 4060, not committed: a dependent chain of 1024 ops on registers, 65535 workgroups of 32, timed per dispatch including submit and wait, main and this branch alternated over 4 runs (best / run medians):

    FLOAT_MODE 0xc0 main this PR
    v_fma_f32 25.3 ms / 34.5-40.5 ms 18.2 ms / 26.8-27.8 ms
    v_add_f32 8.87 ms / 10.2-27.3 ms 9.03 ms / 11.8-17.1 ms
    v_mul_f32 22.5 ms / 25.1-31.8 ms 21.9 ms / 30.1-33.0 ms

    v_add_f32 and v_mul_f32 compile to the same code on both; their spread is this laptop's run-to-run variation. Without any NaN selection v_fma_f32 takes 16.7 ms. The outputs are bit-identical (same checksum).

Touches TranslationContext.hpp and OperandAccess.cpp next to #2035 (quietNan64), in other declarations and functions.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

boykopovar#2083 chooses the NaN result of the f32 fma family on the bits: the first
NaN source, quieted in IEEE mode, with the addend skipped when the
product is infinity times zero, and 0xffc00000 otherwise. The selection
and the infinity-times-zero test ran for every fma, but a NaN source
always gives a NaN result, so for any other result they return it
unchanged.

FPNanResultFma32 carries the result and the three sources, with the IEEE
mode as a flag. The SPIR-V emitter makes the selection, including the
infinity-times-zero test, in a branch taken only for a NaN result, and
the SRT evaluator computes it exactly. Add, sub and mul keep the inline
selects: with two sources a branch costs more than it saves.
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked on Linux (RADV wave64 and CI's lavapipe): agc_driver_f32_arithmetic_nan, f32_denormal_flush, f32_output_modifier, vector_fma_nan_result and the rest of the AGC/libs ctest scope pass. The only two three-source callers (v_fma* family, v_fma_legacy_f32) go through the new op, and passing the result through when it isn't a NaN is equivalent to #2083's selection. Merging; the NVIDIA timings are the author's.

@adriadam10
adriadam10 merged commit c94cbb7 into boykopovar:main Oct 10, 2026
6 checks passed
oneandonlydean added a commit that referenced this pull request Oct 10, 2026
#2279 made agc_driver_f32_arithmetic_nan fail on NVIDIA (Windows):
v_fmamk_f32 returns a quiet NaN where the signalling one is expected
(lane 1 no float mode v_fmamk_f32 is 0x7fc12345, expected 0x7f812345),
5/5 runs on main and 5/5 passing with #2279 reverted. CI's Windows job
skips the AGC execution tests, so it did not show there.

v_fma_legacy was rewritten as a split multiply and add after #2279; it
keeps that and gets back the invalid-product argument it passed before.

Fixes #2581
oneandonlydean added a commit that referenced this pull request Oct 10, 2026
revert(shader): undo #2279's fma NaN result selection
SleglJan pushed a commit to SleglJan/AnyPS5 that referenced this pull request Oct 10, 2026
…-result-guard

perf(shader): choose the fma NaN result only when the result is NaN
SleglJan pushed a commit to SleglJan/AnyPS5 that referenced this pull request Oct 10, 2026
boykopovar#2279 made agc_driver_f32_arithmetic_nan fail on NVIDIA (Windows):
v_fmamk_f32 returns a quiet NaN where the signalling one is expected
(lane 1 no float mode v_fmamk_f32 is 0x7fc12345, expected 0x7f812345),
5/5 runs on main and 5/5 passing with boykopovar#2279 reverted. CI's Windows job
skips the AGC execution tests, so it did not show there.

v_fma_legacy was rewritten as a split multiply and add after boykopovar#2279; it
keeps that and gets back the invalid-product argument it passed before.

Fixes boykopovar#2581
SleglJan pushed a commit to SleglJan/AnyPS5 that referenced this pull request Oct 10, 2026
@karanvora2599
karanvora2599 deleted the perf/shader-nan-result-guard branch October 12, 2026 13:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants