Skip to content

fix(recompiler): avoid illegal narrow constants during optimization - #1343

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
nikhilpanchal1:fix/recompiler-storage-only-constants
Oct 8, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
nikhilpanchal1:fix/recompiler-storage-only-constants

Conversation

@nikhilpanchal1

Copy link
Copy Markdown
Contributor

What

Fixes #1276.

Skip CCP and Simplification for modules with storage-only 8- or 16-bit types, preventing the optimizer from introducing illegal narrow constants. Keep the remaining passes and arithmetic-capable folding without adding device capabilities.

Add optimizer, compiler and Vulkan readback regressions for default, full and none modes.

Tested

Linux x86_64, GCC 14.2.0, Mesa 26.1.6 llvmpipe; the issue's vertex shader and a constant-store compute fixture.

  • Full CTest: 394 passed with SPIRV-Tools enabled; 391 passed with it disabled. No failures; 23 skips in each run due to userfaultfd and subgroup limits.
  • All nine new tests repeated three times with shader caches disabled: 27 passes, including wave32/wave64 Vulkan readback.
  • ASan/UBSan optimizer checks passed in all three modes.
  • Contribution checks passed.

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

@Dextroid17 Dextroid17 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Built and ran it here — external PRs get no CI in this repo, so this is a local run: Linux/CachyOS, GCC, Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON, NVIDIA RTX 3080 Ti. This is a comment, not an approval — I don't have merge rights here.

It works, and the new tests bite

Built agc_spirv_narrow_constant_store_tests, agc_spirv_optimizer_tests and agc_driver_constant_store_tests. All nine registered names (3 targets × default/full/none) pass; full suite 417/417 (408 on main, +9).

The part worth recording is the negative: taking only SpirvOptimizer.cpp from upstream/main and keeping your tests, 4 of the 6 SPIR-V tests fail with the issue's exact line —

i16 vector storage only: SPIR-V validation after optimization failed:
level=2 word=22: Cannot form constants of 8- or 16-bit types
  %short_0 = OpConstant %short 0

and the two _none runs pass, correctly, since they never optimize. So the regression is real on main and the new tests are load-bearing rather than presence checks.

The default pipeline is unchanged

The restructure is the risky half, so I extracted the pass sequence for the folding-allowed default path from both revisions and compared them mechanically: 22 passes, identical, same order. Registering PerformancePasses only for full && allowFolding does not change what a module without narrow types already got.

DotProductInput4x8Bit is right, and it is the subtle bit

I expected this to be a false negative and it is not. spirv-val accepts a module that declares DotProductInput4x8Bit with no literal OpCapability Int8, and the reason is ValidationState_t::RegisterCapability (vendored SPIRV-Tools, source/val/validation_state.cpp:386) recursing over the grammar's required capabilities — the grammar gives DotProductInput4x8Bit "capabilities": ["Int8"]. So the validator has Int8 while a scan of the declared lines alone would not. Treating it as int8-available avoids dropping folding for those modules.

The direction is also right: the scan's capability set is a subset of the validator's, so allowFolding can only be more conservative than validation — it can never wave through a fold that the validator would then reject. That is the property that matters, and your test matrix (widths 8/16, signed and unsigned, float16, StorageBuffer8BitAccess, StorageBuffer16BitAccess, DotProductInput4x8BitKHR) is what pins it.

Non-blocking

  • HasLimitedUseTypes reads spirv[0] and wordAt(offset + 1 | 2) with no size or instruction-length guard; a zero word count would also not advance offset. It is unreachable today only because the scan runs after tools.Validate(...), which rejects a truncated module first. If that ordering is not meant to be load-bearing, a cheap guard would stop a later move of the call from reading past the span.
  • The two _none registrations are always green by construction (nothing optimizes), so of the six SPIR-V tests four are the ones that can fail. Fine as a mode matrix, just worth knowing when reading a red job.

Caveat: the GPU-backed agc_driver_constant_store* runs here used APS5_WRITE_WATCH_IMPORTS=watch, because this branch predates #1329 and the NVIDIA dma-buf probe aborts those tests otherwise. The SPIR-V-only tests need no device and ran normally. Not run on Windows.

Written with AI assistance.

@gunerky

gunerky commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Additional Windows / NVIDIA validation

Tested PR head c7d4638dca3aca9b0ea41608c8f862461de8675c.

Tested

Windows 11 Pro (26300), MinGW-w64 GCC 15.2.0 UCRT/posix/SEH, NVIDIA RTX 3060, Windows driver 32.0.16.1742. Release, BUILD_TESTING=ON, ANYPS5_ENABLE_SPIRV_TOOLS=ON; default build and libs target succeeded.

Both runs used this PR's existing fixtures. For the baseline, only SpirvOptimizer.cpp was replaced with the version from main at df16c4c256be3c44e03eb9149a6ce5f8e8a038b2, then restored and rebuilt for the PR run.

  • Main optimizer plus PR tests: 407 passed, 6 failed, 0 skipped. Default/full optimizer, compiler and GPU constant-store cases reproduce Cannot form constants of 8- or 16-bit types; all three none cases pass.
  • PR optimizer: 413 passed, 0 failed, 0 skipped.
  • All nine new tests repeated three times: 27 passes, with shader caches disabled and Vulkan required. Physical RTX 3060 selection is confirmed in the logs; wave32/wave64 guest constant-store readback passes in default/full/none.

Scope

This is additional test evidence. No source change is proposed. No game execution, optimization performance benchmark, sanitizer run or other GPU vendor was tested here.

The unmodified Windows conventions checker reports a false doc-link error for an existing target. Its source is unchanged by this PR; #1254 addresses the same path-separator issue. A diagnostic rerun normalizing link-result separators in memory passed all conventions checks; the tracked checker was not edited.

AI-assisted: yes — Codex set up and ran the validation and drafted this summary.

@Dextroid17

Copy link
Copy Markdown
Contributor

Recording that the negative control now reproduces on two platforms, since that is what the fix rests on: @gunerky measured 407 passed / 6 failed with the main optimizer on Windows 11 + RTX 3060, and I measured the same 6 failures with the same signature (Cannot form constants of 8- or 16-bit types) on Linux with an RTX 3080 Ti, using the same method — only SpirvOptimizer.cpp swapped back to main with the PR tests kept. Two platforms, one signature, so the failure is a property of the optimizer and not of a driver or a platform quirk. Nothing to change here; it is evidence I would have wanted on the record.

Written with AI assistance.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 8, 2026
@nikhilpanchal1
nikhilpanchal1 force-pushed the fix/recompiler-storage-only-constants branch from c7d4638 to d3dc02a Compare October 8, 2026 06:30
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 8, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 8, 2026
Skip CCP and Simplification for storage-only narrow types while keeping
the remaining passes and arithmetic-capable folding.

Add compiler and Vulkan readback regressions for default/full/none modes.

Tested: Linux/llvmpipe, full CTest with SPIRV-Tools on/off, ASan/UBSan
optimizer checks, and cold-cache Vulkan wave32/wave64 readback.

Fixes boykopovar#1276
Check module headers, instruction lengths and required operands before
scanning narrow types.

Cover malformed and byte-swapped modules while retaining the native
SPIR-V 1.3 conversion matrix.

Tested: Linux/llvmpipe CTest three times with SPIRV-Tools on and off,
cold-cache Vulkan regressions, and ASan/UBSan in all optimization modes.
@adriadam10

Copy link
Copy Markdown
Collaborator

Rebased on current main (conflicts were only adjacency in libSceAgcDriver/CMakeLists.txt and TechnicalDebt.md, kept both sides). Local build + ctest 448/448 green.

  • The fix matches ANYPS5_ENABLE_SPIRV_TOOLS: CCP turns a constant byte store into an 8-bit OpConstant that the module cannot declare #1276: CCP/Simplification are the passes that create the 8/16-bit OpConstant, and skipping them only for storage-only narrow modules keeps the rest of the pipeline.
  • The new optimizer tests only exist with ANYPS5_ENABLE_SPIRV_TOOLS=ON; our local build has it off, so I rely on the Linux CI job (it enables it) plus the Linux/Windows negative controls posted above (6 failures with main's optimizer, 0 with this PR).
  • Cost to note, already in TechnicalDebt: every module with byte stores now loses CCP/Simplification.

Merging once CI is green on the rebased head.

@adriadam10
adriadam10 force-pushed the fix/recompiler-storage-only-constants branch from d3dc02a to 39e8e70 Compare October 8, 2026 11:28
@adriadam10
adriadam10 merged commit f0cc675 into boykopovar:main Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build docs needs rebase Conflicts with main, rebase needed shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ANYPS5_ENABLE_SPIRV_TOOLS: CCP turns a constant byte store into an 8-bit OpConstant that the module cannot declare

4 participants