Skip to content

upstream PRs: BDA and memory emission in the shader output (U2) - #473

Merged
RINNECODER merged 7 commits into
mainfrom
upr1/bda-memory-emission
Oct 13, 2026
Merged

RINNECODER merged 7 commits into
mainfrom
upr1/bda-memory-emission

Conversation

@RINNECODER

@RINNECODER RINNECODER commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

Ports unit U2 of the upstream open-PR batch (BDA lookups and memory emission in the recompiler output). Upstream reports smaller shader code and faster driver compiles on AMD; neither GPU time nor Metal compile time has been measured on Metal yet. boykopovar#3655 and boykopovar#4931 replace unrolled reads with loops, so Metal GPU time needs a measurement before claiming a gain. Every PR is its own commit, and each commit body says "Ported from upstream PR #N".

Refs upstream-pr-batch-1

Ported

No ShaderDiskCache FormatVersion or request-serializer bump. None of these PRs change the payload layout, and emitter changes already invalidate cache entries through the cache's source version.

Skipped

Evidence

  • Guest-replay config (utilities-only, shader bridge, bridge tests, guest replay; arm64): ctest: 100% passed, 0 of 37 failed.
  • agc_formatted_buffer_once_tests (x86_64): passes. A GPU-selected formatted load drops from 114447 to 16821 SPIR-V words, compared with a main build. A known-descriptor load is 1947 words on main and 2638 here.
  • agc_shader_memory_tests cannot link on macOS, on main or on this branch, because GuestMemory.cpp uses pthread_getattr_np. Its verify functions were run one at a time with a stub GuestMemory, on this branch and on main. Both gave the same result: 32 passed, including verifyBdaReadFallbackFunctions, which carries the new fix(shader): keep each BDA page-table walk to one inlined copy per read boykopovar/AnyPS5#3655 and perf(shader): read adjacent BDA dword loads of one block through one span read boykopovar/AnyPS5#4931 checks. Both also fail verifyPixelRequestSerialization (it expects serialization version 19) and stop with a bus error at verifyGuardedNullPointers (this needs the real guest-memory fault handling). Neither of those comes from this change.
  • The Vulkan execution tests added upstream (GpuBufferFormat, ScalarBufferGpuDescriptor, BdaExecution) need the Vulkan device path and were not run on macOS.
  • Game smoke: one run, 300 s, under the eboot lock, plugged in, with a private image clone and private shader and relink caches. The title reached the pre-game "Begin Game" screen at about 286 s, which matches the recorded main baseline at 300 s; main reaches the 3D map at about 296 s, so the map is outside a 300 s run and no map fps was recorded. 54 frames_presented events, 2860 flips by 286 s, 0 [metal] skipped draws or dispatches, no shader conversion failures, and a clean guest exit.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj

EdouardSence and others added 7 commits October 13, 2026 17:15
RADV inlines every SPIR-V function, DontInline included, so every
get_bda_pointer call site becomes its own copy of the page-table search,
and NIR's copy propagation of variables grows with each copy. The lookup
now keeps its search bounds in phis and records its header and range
faults from one call site each, picking the first failing check; a byte
read and the byte fallback of a span read loop over one lookup instead of
unrolling one per byte or dword.

(cherry picked from commit 2d3a970)

Ported from upstream PR boykopovar#3655

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A formatted load whose V# is selected on the GPU emitted all 77 format
cases in its shared function, and drivers that inline every function,
like RADV, copied all of them into each call site. The load now reads the
format's component count, widths, byte and bit offsets and type from a
constant table and runs one path for its four components, with the
existing conversion of each (type, width) pair selected at run time.
Stores and loads through a prepared V# keep the format switch.

(cherry picked from commit 369928d)

Ported from upstream PR boykopovar#3656

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agc_driver_gpu_buffer_format loads the V#'s records and word3 from memory
and moves them into SGPRs with v_readfirstlane, so the recompiler sees a
GPU-selected descriptor and the load goes through the format table. It
checks buffer_load_format x/xy/xyz/xyzw and d16_xyzw on two lanes for
formats 1-77, selectors 0/1/4-7 on 1-, 2- and 4-component formats, loads
with a component past NUM_RECORDS, and that formats 0 and 78 stop the
invocation before its stores.

The expected values were captured on the format switch (2d3a970) and
are identical with the table.

(cherry picked from commit 6aaae03)

Ported from upstream PR boykopovar#3656

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every access by guest address calls a lookup that binary-searches the
BDA range table: about 11 dependent steps over ~1200 ranges, 34 table
loads per access. Since boykopovar#639 every buffer access goes through it.

The lookups (read, probe, write and atomic) now keep the last range a
search of the invocation resolved in four private variables: guest
begin and end, device base and permissions. An access inside that range
with the wanted permissions returns base + (address - begin) at once.
Anything else (another range, a missing permission, an unmapped,
zero-byte or wrapping access) takes the full search with every check
and fault it records today, and a successful search refills the cache.
The variables only hold what the table says, and no shader writes the
table, so the result is the search's own answer.

On one title's busiest scene (Windows, discrete GPU), GPU time of the draws 59.5 -> 35 ms per frame.

APS5_NO_BDA_LOOKUP_CACHE=1 emits the lookups without the cache. The
switch is part of the shader disk cache key.

(cherry picked from commit 443583a)

Ported from upstream PR boykopovar#2837

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…span read

A scalar load of several dwords (s_load_dwordx8) becomes one
LoadAddressU32 per dword, and each took its own span read: a probe of
the page table and a fallback that looks every byte up. Loads in one
block with equal address operands and offsets within 16 bytes of the
first now share its span read. Instructions that write memory end a
group; the address operands are compared by content, since each load
carries its own GetAddressResource and SelectU32 values.

A title's main-menu pixel shader goes from 136 span reads
to 34. AMD's Windows compiler inlines the read helpers at every call,
and did not finish that shader before.

(cherry picked from commit 4577633)

Ported from upstream PR boykopovar#4931

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An s_buffer_load_dword* whose V# the recompiler reads at run time (a
GPU-selected V#, for example one built with v_readfirstlane) went
through the vector buffer descriptor checks. A nonzero TYPE in word 3
made it return 0 and record an invalid-descriptor fault, and the base
was used with its two low bits, so a base off a dword boundary read
shifted bytes.

SMEM uses only the base, stride and num_records of the V#, and the two
low bits of the base and of the final address are ignored (AMD RDNA2
ISA guide, 30 November 2020, section 7.1 Table 27 SBASE and section
7.2.1 "Reads using Buffer Constant"). The capture-time evaluator
already follows both rules, and the bound path already drops the two
low bits. For scalar buffer loads the runtime path now masks the base
and skips the TYPE check; vector, formatted and atomic accesses keep
it.

agc_driver_scalar_buffer_gpu_descriptor runs s_buffer_load_dword and
s_buffer_load_dwordx2 through such a V# with the base 0 to 3 bytes off
a dword and TYPE 0 to 3, in wave32 and wave64.

(cherry picked from commit 86fdb52)

Ported from upstream PR boykopovar#5161

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…compute modules

(cherry picked from commit 573ba3b)

Ported from upstream PR boykopovar#5353

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RINNECODER RINNECODER added the upstream-sync Upstream boykopovar/AnyPS5 intake label Oct 13, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T22:56:56.274087Z aea7c5c PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 13, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: dc942610-0fe3-4007-ad95-2d872e2aee24
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aea7c5c20a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const auto none = state.module.AllocateId();
state.module.AddFunction(spv::OpCompositeConstruct, vector, none, zero, zero, zero, zero);
const auto all = EmitValueOrDefaultIfCondition(state, ActiveArgument(ctx, inst), vector, none, [&]() {
const auto values = EmitBdaDwordReads(ctx, inst, GuestAddressBase(ctx, inst, mem), mem.offset, 4u, false, used);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve each grouped load's fault PC

When separate scalar load instructions use the same base with nearby offsets, AdjacentBdaLoads can group them even if their MemoryFlags::pc values differ, but this call emits every lookup and fallback using only the first instruction's PC. If a later grouped offset is unmapped or otherwise faults, the fault record therefore identifies the earlier instruction, making the reported shader location incorrect. Restrict grouping to members with the same PC or preserve per-member PCs in the grouped fallback.

Useful? React with 👍 / 👎.

@RINNECODER
RINNECODER merged commit b1b1a34 into main Oct 13, 2026
6 checks passed
@RINNECODER
RINNECODER deleted the upr1/bda-memory-emission branch October 13, 2026 23:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc shader upstream-sync Upstream boykopovar/AnyPS5 intake

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants