Skip to content

perf(shader): fold masks, shifts and field extracts through bits they do not use - #7673

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
tibzejoker:perf/fold-unused-bits
Oct 20, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
tibzejoker:perf/fold-unused-bits

Conversation

@tibzejoker

@tibzejoker tibzejoker commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

What

The translator splits and merges the 16-bit halves of 32-bit registers on every f16 or packed instruction. As a result, the IR carries many masks, shifts and field extracts that do not change any bit their user reads. This change teaches the constant folder which bits a value can hold (masks, merges, shifts, field extracts, 16- and 8-bit widenings, selects) and which bits a user reads. It applies these exact rewrites:

  • A mask that clears no bit the value can hold is dropped, and so is a merged part with no bit under the mask.
  • And(And(x, a), m) becomes And(x, m) when m keeps no bit outside a. A rewritten mask is folded again: Fold visits each instruction once, so chains used to survive.
  • A field extract, shift, mask or 16-bit truncation reads through masks that keep the bits it reads, and through merged parts that hold none of them.
  • ShiftRightLogical(ShiftLeftLogical(x, k), k) becomes And(x, ~0u >> k).
  • BitFieldUExtract(x, 0, n) is x when x has no bit at or above n.
  • Or of two masked parts of one word becomes that word under the union of the masks, or the word itself when the masks cover it.
  • A field extract of a left shift reads the unshifted word.

Every rewrite gives the same bits, so shader results do not change.

Tested

Windows 11, MinGW GCC 15.2, RTX 4090 (616.92):

  • agc_constant_bit_test gets six new cases: repeated, narrower and wider masks, a mask under a merge, half extractions, halves that rejoin. All six fail on main.
  • ctest -R ^agc_: 726/727. The failure is agc_driver_bda_device, which fails on main too.

Size and compile time, GTA V (PPSA04264) compute shaders dumped with APS5_DUMP_SHADERS:

main this PR
SPIR-V words, 79 compute requests (agc_shader_replay) 2,226,143 2,149,916 (-3.4 %)
compute 0x25b390800: words / cold NVIDIA pipeline compile 647,769 / 7,255 ms 583,361 / 6,266 ms
compute 0x25b393000 (FSR1 RCAS) 116,581 / 528 ms 105,263 / 492 ms
compute 0x25b3bdc00 142,905 / 681 ms 142,802 / 668 ms
compute 0x25b3a2a00 57,427 / 251 ms 57,320 / 249 ms

How the compile times were measured:

  • Pipelines were captured with APS5_DUMP_COMPUTE_PIPELINES through the driver's dispatch path.
  • Each capture was replayed with agc_compute_pipeline_replay. pipeline_create is the median of 3 replays, each with a distinct OpSourceExtension so that the NVIDIA driver cache misses.

Output check: the same rewrites were run on a local test branch, where 71 of these compute shaders were dispatched on synthetic memory with and without them. Every byte they wrote was identical on the RTX 4090.

Not attached: frame traces. This changes pipeline creation time (stutter while shaders compile), not the steady-state frame.

Performance capture

GTA V (PPSA04264), open world from the same save, RTX 4090, one run each, same session. Base = main 47a266c + #6488 + #6848 + a local bench aid (keyboard pad input while an IME session is open), because GTA V does not run on plain main; cdeb62157e5e is this PR on top of it. Traces: fork release perf-7673-7677-20261019.

These frame times do not measure this change. The trace buffer of queue worker 0 fills after about 3.7 s (trace_dropped), and in the open world frame time follows where the save respawns. This PR shortens shader compiles: the SPIR-V word counts and the cold compile times under Tested are the measurement.

{"commit":"6537538e26d6","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"330:60"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 616.92","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 330:60","first_present":4184,"ms":60074.435},"presents":416,"frame_ms":{"p50":129.613,"p90":188.599,"p99":328.411,"max":548.842},"gpu_busy_ms_per_present":47.351,"worker_busy_pct":{"queue worker 0x0":3120.864,"queue worker 0x20":33.818,"queue worker 0x28":19.713,"queue worker 0x38":29.948,"queue worker 0x30":12.619},"top_phases_ms_per_frame":{"Driver.EndSubmission":4370.075,"Driver.Packet":271.688,"Driver.Packet.memory_wait":136.191,"Driver.Packet.draw":114.943,"Driver.Draw":113.961,"Driver.Draw.shader_compile_and_link":62.672,"Shader.DrawStage":48.422,"Driver.Draw.draw_enqueued":39.814},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":3.742,"events_lost":3289625,"totals_full":false}},"trace":"aps5-trace-6537538e26d6-44248.json.gz"}
{"commit":"cdeb62157e5e","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"330:60"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 616.92","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 330:60","first_present":3866,"ms":60081.484},"presents":346,"frame_ms":{"p50":171.733,"p90":179.056,"p99":223.195,"max":481.729},"gpu_busy_ms_per_present":59.529,"worker_busy_pct":{"queue worker 0x0":3379.177,"queue worker 0x28":21.195,"queue worker 0x30":14.393,"queue worker 0x20":31.214,"queue worker 0x38":30.700},"top_phases_ms_per_frame":{"Driver.EndSubmission":5701.379,"Driver.Packet":329.999,"Driver.Packet.memory_wait":166.871,"Driver.Packet.draw":137.103,"Driver.Draw":135.762,"Driver.Draw.shader_compile_and_link":96.533,"Shader.DrawStage":75.611,"Shader.DrawStage.materialize":37.441},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":3.712,"events_lost":4041886,"totals_full":false}},"trace":"aps5-trace-cdeb62157e5e-39304.json.gz"}

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 under Silent stubs in their library's technical debt file
  • 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
  • Fixes: none
  • Unblocks: none
  • GPUs tested: NVIDIA GeForce RTX 4090 (616.92, Windows 11); AMD Radeon 860M (32.0.13058.8001, Windows 11, by @LeandroLP23)
  • AI-assisted: yes (Claude Code)

🤖 Generated with Claude Code

https://claude.ai/code/session_01XBXM4pgkMHYEtqN1EQni2P

… do not use

The constant folder now knows which bits a value can hold (masks, merges,
shifts, field extracts, 16- and 8-bit widenings, selects) and which bits a
user reads:

- a mask that clears no bit the value can hold is dropped, and so is a
  merged part with no bit under the mask;
- And(And(x, a), m) becomes And(x, m) when m keeps no bit outside a, and a
  mask rewrite is folded again;
- a field extract, shift, mask or 16-bit truncation reads through masks
  that keep the bits it reads and through merged parts that hold none;
- a right shift of a left shift by the same amount becomes a mask;
- BitFieldUExtract(x, 0, n) is x when x has no bit at or above n;
- Or of two masked parts of one word becomes the word under the union of
  the masks (the word itself when they cover it);
- a field extract of a left shift reads the unshifted word.

These patterns come from 16-bit halves of 32-bit registers (f16 and packed
math), which the translator splits and merges per instruction.

Test: agc_constant_bit_test gets six cases (repeated, narrower and wider
masks, a mask under a merge, half extractions, halves that rejoin); all six
fail on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XBXM4pgkMHYEtqN1EQni2P
@github-actions github-actions Bot added perf Performance change perf numbers missing perf pull request without a before and after summary line labels Oct 18, 2026
@github-actions

Copy link
Copy Markdown

This pull request is labeled perf, so it needs the summary line of a capture on main and one with the change, from the same title, scene and machine, as the Performance report form asks. Please paste both aps5-summary.jsonl lines, attach the two traces and add the output of tools/trace_digest.py.

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 5021 core/libs/prx/libSceAgcDriver/tests/ConstantBitTest.cpp
core/shader/recompiler/Optimization/src/ConstantFolder.cpp
PR 5500 core/shader/recompiler/Optimization/src/ConstantFolder.cpp

@github-actions github-actions Bot added shader core/shader agc libSceAgc* labels Oct 18, 2026
@LeandroLP23

Copy link
Copy Markdown
Contributor

AMD run, since the description covers an RTX 4090. Radeon 860M (RDNA 3.5, driver 32.0.13058.8001), Windows 11 26300, WinLibs GCC 15.2.0 r7, Release, BUILD_TESTING=ON. Build: main 8a456334f with #7677, #7673, #7671, #7666, #7676, #7675, #7672, #7668, #7664 and #7658 merged together (each merge clean, built once).

agc_constant_bit_test and agc_driver_bda_device pass 3 runs of 3 on AMD's proprietary driver.

AI-assisted

@tibzejoker

Copy link
Copy Markdown
Contributor Author

About the performance-report request: I captured the requested warm-cache traces (GTA V PPSA04264, open world, RTX 4090). Two things make them a poor fit for this change:

  • With the trace on, the queue worker's event buffer fills within about 3.7 s in this scene (trace_dropped: true, about 3.3 M events lost), so the per-phase totals are not usable. Only the frame percentiles survive.
  • In the open world, the frame time depends mostly on where the save respawns: the same base build ran at 9.3 FPS in its warm-up and 5.5 FPS in its traced run. Shader compilation inside the traced minute also varies between runs (62 to 97 ms per frame of Driver.Draw.shader_compile_and_link), depending on which new shaders that run meets.

This change only makes modules smaller. Its effect is on pipeline creation, so I measured that directly: cold NVIDIA pipeline_create, median of 3 agc_compute_pipeline_replay runs, each with a distinct OpSourceExtension so the driver cache misses. The numbers are in the description. If you'd rather have a specific trace or scene, tell me which and I'll capture it. The three traces I took are here, for completeness: https://github.com/tibzejoker/AnyPS5/releases/tag/perf-7673-7677-20261019

AI-assisted (Claude Code).

@savvaniss

Copy link
Copy Markdown
Contributor

Intel and AMD Windows data: agc_constant_bit_test fails on main and passes with this PR, on an Intel UHD 770 and an AMD RX 9070 XT.

Windows 11 Pro 26300, WinLibs GCC 15.2.0 r7, Release, every execution test built, ANYPS5_NO_SHADER_CACHE=1, serial CTest. Intel UHD Graphics 770 (8086:a780), driver 32.0.101.7088. AMD Radeon RX 9070 XT (1002:7550), driver 32.0.31041.1004, with VkCache emptied first. This head 55bb2c793 merged into main 07377573f. "Without" means this PR's test files on main 07377573f, with none of its other changes.

Tests, ×3 UHD 770 RX 9070 XT
This head: agc_constant_bit_test pass pass
Without fails fails

This is a correctness check only; I measured no speed.

I also ran full CTest with this PR and the 13 other open AGC and shader PRs I tested in the same batch merged together (#7668, #7659, #7504, #7432, #7426, #7413, #7480, #7465, #7466, #7464, #7449, #7489, #7560). Neither GPU showed a failure beyond main's own: the five image-table tests (#6647) and agc_driver_sample_multisample_target (#6957) on the UHD 770, and agc_driver_layered_volume_vertex_no_gpl (#4768) on the RX 9070 XT.

AI-assisted: yes (Claude Code)

@tibzejoker

Copy link
Copy Markdown
Contributor Author

Thank you @savvaniss: RDNA4 (RX 9070 XT) and the UHD 770 both fail on main and pass with the head, which is the behaviour the test is meant to pin down. Good to see it holds with the 13 other PRs merged together too.

AI-assisted: yes (Claude Code)

@github-actions github-actions Bot removed the perf numbers missing perf pull request without a before and after summary line label Oct 19, 2026
@adriadam10 adriadam10 self-assigned this Oct 19, 2026
@adriadam10
adriadam10 merged commit 1b0ff1e into boykopovar:main Oct 20, 2026
16 of 17 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the demanded-bits rewrites (and/or/xor merges, shifts, field extracts, 16-bit truncation, SRL(SLL) and masked-part merging) against the IR semantics: each keeps every bit its user reads. The six new agc_constant_bit_test cases pin the folded shape and fail on main; the NVIDIA, AMD and Intel runs in the thread agree; merged.

oneandonlydean pushed a commit that referenced this pull request Oct 20, 2026
#7018 reads the low-half factors of v_mul_lo_u16 and v_pk_mul_lo_u16
as 24-bit fields because AMD's Windows driver stores the full 32-bit
product when it knows both factors fit in 16 bits. #7673's fold of
BitFieldUExtract(x, 0, n) into x removed those reads when the program
had masked the factors to 16 bits, so agc_driver_mul_lo_u16_high_half
failed again on that driver. The folder now keeps a field read that a
multiply uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* perf Performance change shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants