Skip to content

feat(shader): execute bounded S_CALL_B64 - #1535

Merged
adriadam10 merged 3 commits into
boykopovar:mainfrom
Sanjaysrinivas:feat/shader-static-call
Oct 8, 2026
Merged

adriadam10 merged 3 commits into
boykopovar:mainfrom
Sanjaysrinivas:feat/shader-static-call

Conversation

@Sanjaysrinivas

@Sanjaysrinivas Sanjaysrinivas commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

What

Execute bounded S_CALL_B64 subroutines through the existing static call/return model. With the prerequisites alone, the relative call decodes but its return is rejected as a dynamic s_setpc_b64. This follow-up resolves the static target, saves the next instruction address, links the paired return, and includes forward callees after s_endpgm in decoding.

Dependencies: none outstanding. #1217 and #895 have both merged into main. Their duplicate prerequisite commits are dropped from this branch; ebosido444-ai and Kikoarl retain authorship in upstream history. This PR contains only the three bounded-CALL follow-up commits.

Related open PR #1523: its GraphBuilder performance work must preserve the scalar-call metadata now supplied by main and this follow-up's CALL and link-lifetime checks. Neither feature depends on the other. Rebase and combine the precomputed branch-target scans when that contribution lands.

The branch is rebased onto current main 4ddbf0497dbdd16f885fb27d30941a772185370d and contains only the three follow-up commits:

ee0b1109 feat(shader): execute bounded S_CALL_B64
4d2917e6 fix(shader): guard scalar call link lifetimes
2b2a0c09 test(shader): report effective call targets

The integration keeps upstream CALL decoding, the newer prepared-shader code, and #1217's fetch-shader registration fix and execution regression. External fetch translation remains main's implementation. CALL is handled by the shared bounded call translator with a single switch case. The execution fixture uses descriptor word 0x31016fac, matching main's existing scalar GPU fixtures and their raw-buffer bounds mode; its instruction words and expected arithmetic outputs are unchanged.

The new runtime work is in the execution and guard commits; the last commit makes the test evidence reflect actual device capabilities. It reuses the prerequisite analyser and link translation rather than introducing a second call framework. No coverage script or reported percentage is edited.

Semantics come from AMD's RDNA2 ISA reference, sections 3.6.3 (SGPR alignment), 12.2 (S_CALL_B64) and 12.3 (S_GETPC_B64, S_SETPC_B64, S_SWAPPC_B64). CALL saves PC + 4 and uses a signed, dword-scaled relative displacement. LLVM 18 independently assembled all five fixtures for both gfx1013 and gfx1030; all ten .text sections match the test words exactly, with no unresolved text relocations. Assembling a target is an encoding check, not native execution on that target.

Supported calls have static targets, distinct ordinary aligned SGPR link pairs, one exact paired return, and nested or disjoint regions. Unsupported targets, recursion, shared links, escaping link values, overlapping tuples/descriptors, relative SGPR reads, and cross-region branches/fall-through throw. The analyser checks all decoded instructions, including unreachable code. Arbitrary indirect subroutines and general call frames remain unsupported. The inherited vertex fetch path remains separately documented in docs/dev/TechnicalDebt.md.

Tested

Current rebase: 2b2a0c09b4238c188b9487ca9d4ad9fb2e609831 on main 4ddbf0497dbdd16f885fb27d30941a772185370d. Normal Release CMake targets were rebuilt with GCC 13.3 on Ubuntu 24.04/WSL2, SPIRV-Tools integration disabled. All eight focused CTest cases passed three repetitions with Vulkan required, shader caching disabled, and lavapipe, including upstream agc_driver_scalar_swappc_fetch. The static-call fixture retains five variants, ten dispatches, 480 active lanes and 1,920 output comparisons per execution; both requested host targets report subgroup 8. Fresh full GitHub CI is pending; earlier full CI, native encoding checks and captured modules below identify their original revisions.

Earlier validation at head 871e6317 (retained for encoding and capture evidence):

Ubuntu 24.04 under WSL2, GCC 13.3, CMake 4.4.4, Ninja 1.13.2. Normal Release CMake targets build the shader compiler, driver/runtime dependencies and tests from this worktree. The first configure reused host feature-detection cache values from an earlier build on the same host; compiler and driver objects were built in a new build directory. FFmpeg uses the existing prepared repository dependency package. No old manually linked candidate compiler is used for the CMake tests.

Vulkan execution uses lavapipe / llvmpipe LLVM 20.1.2, physical subgroup size 8. ANYPS5_REQUIRE_VULKAN=1 makes unavailable Vulkan fail rather than skip; shader caching is disabled. No native RDNA2 or PS5 execution, local Windows build, title run, or general indirect-call correctness is claimed. These limits and the supported model are recorded in the existing technical-debt document.

At the preceding head 2a3616e9, GitHub Windows builds/tests and all three Relinker jobs passed. The Linux job failed before compilation at the test-registration check: guest_formatting_tests was already registered with CTest but remained in the non-Windows KNOWN_UNRUN exception list. Upstream main removes that stale exception in 59fd2022; this refresh picks up that fix by rebasing. The original CI check now passes locally. GitHub CI now passes Linux and Windows builds/tests and all three Relinker jobs at head 871e6317 (successful workflow run); conventions and the progress report also passed. The seven focused checks ran without skips locally and in Linux CI. Windows CI skipped the Vulkan execution fixtures, so its successful job does not establish Windows GPU execution. The captures below identify the revision tested locally.

Build and run the focused checks from a normal checkout with its submodules initialized:

cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON
cmake --build build --parallel 8 --target \
  scalar_call_tests scalar_swappc_tests agc_shader_control_flow_tests \
  agc_driver_scalar_static_call_tests agc_driver_scalar_setpc_tests \
  agc_driver_scalar_swappc_tests agc_driver_scalar_swappc_fetch_tests agc_driver_branch_past_endpgm_tests

ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 \
VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.json \
ctest --test-dir build --output-on-failure -V \
  -R '^(scalar_call|scalar_swappc|agc_shader_control_flow|agc_driver_(scalar_static_call|scalar_setpc|scalar_swappc|scalar_swappc_fetch|branch_past_endpgm))$'

Use the appropriate Vulkan ICD path for the host. The seven checks pass with zero skips. Expected integer results are calculated from runtime input buffers using add/XOR formulas, independently of the translator. The execution matrix is:

Fixture Guest wave32, subgroup 8 Guest wave64, subgroup 8 Output comparisons
SWAPPC forward Pass, 32 lanes Pass, 64 lanes 384
CALL forward Pass, 32 lanes Pass, 64 lanes 384
CALL backward Pass, 32 lanes Pass, 64 lanes 384
CALL callee after endpgm Pass, 32 lanes Pass, 64 lanes 384
CALL nested inside SWAPPC Pass, 32 lanes Pass, 64 lanes 384

Total per final matrix run: 10 dispatches, 480 active lanes, 1,920 output comparisons, plus 640 inactive-output sentinel checks. The backward fixture uses a negative SIMM16. The mixed fixture uses distinct outer/inner link pairs.

Lavapipe reports both its default target and ComputeTarget(32) as subgroup 8. An earlier matrix executed a third, duplicate configuration and passed 3,200 comparisons; that did not establish a distinct wave32 host path. The final test reports the effective target sizes and runs the additional wave64-on-wave32 case only when it differs from the default target. A genuine 32-wide host path remains untested locally.

Before the extension, the existing decoder/SWAPPC/SETPC/branch-past-END checks passed, and the new execution fixture failed on the CALL return at PC 0x34 after successfully running SWAPPC. Seven additional cases were each incorrectly accepted before the guard fix; each now throws. These before-fix captures predate the main refreshes; the call implementation and guard test files were unchanged by the main refreshes. The matrix was subsequently deduplicated as explained above.

Guard case Before fix Final behaviour
An 8-dword scalar load spans the link pair Accepted Throws
Return reads the high register as its pair base Accepted Throws
Ordinary fall-through enters a callee Accepted Throws
Conditional branch's fall-through enters a callee Accepted Throws
Paired return precedes its target region Accepted Throws
Buffer descriptor spans the link pair Accepted Throws
Relative SGPR read can reach the link pair Accepted Throws

The scalar regression executable accepts the individual case names tuple, wrong_return, fallthrough, conditional_fallthrough, return_before_target, descriptor, and relative_read. With no argument it runs every case and the positive tests, including the first and last supported link pairs.

An instrumented local copy of the execution fixture captured all 10 modules from the same CMake-built compiler/driver. All validate with SPIRV-Tools under vulkan1.1spv1.4. File capture is confined to that local diagnostic copy; no test-only dumping interface is added to production code. A negative control flips only the expected first output bit and fails as intended:

AGC graphics: SWAPPC forward wave32: lane 0 add is 0x13579bdf, expected 0x13579bde
Fixture assembly used for the independent encoding checks

All fixtures begin with:

v_lshlrev_b32 v1, 4, v0
buffer_load_dwordx4 v[4:7], v1, s[0:3], 0 offen
s_waitcnt vmcnt(0)

The shared callee arithmetic is:

v_add_nc_u32 v4, 0x13579bdf, v4
v_xor_b32 v5, 0xa5a5a5a5, v5
v_add_nc_u32 v6, v6, v7
v_xor_b32 v7, 0x5a5a5a5a, v7

Forward CALL:

s_call_b64 s[8:9], callee
s_branch finish
callee:
; shared callee arithmetic
s_setpc_b64 s[8:9]
finish:
buffer_store_dwordx4 v[4:7], v1, s[4:7], 0 offen
s_endpgm

Backward CALL places the callee before the caller, initially branching over it. The after-END fixture places the store and first s_endpgm immediately after CALL, with the callee and its paired return after that END. The mixed fixture calls its outer region with SWAPPC and an inner region with CALL through s12:s13; the outer region returns through s8:s9. The exact instruction words are in the committed ScalarStaticCall.cpp.

Final validation captures

Validation revision: 871e6317 rebased onto main 4b005b22.

The same Linux registration check against the same configured build graph, before and after the upstream script fix:

Before: exit 1
error: guest_formatting_tests is run by ctest now; remove it from KNOWN_UNRUN
After: exit 0
note: agc_driver_mesh_tests is built and not run, as recorded
note: agc_driver_recorder_tests is built and not run, as recorded
note: video_out_flip_tests is built and not run, as recorded
415 test executable(s), 3 built and not run as recorded, none new

Reproduction command (run with the script from the indicated revision):

python3 tools/check_test_registration.py build

Focused CTest results:

1/7 Test  #84: agc_shader_control_flow ..........   Passed    0.03 sec
2/7 Test  #89: agc_driver_branch_past_endpgm ....   Passed    0.20 sec
3/7 Test #215: agc_driver_scalar_setpc ..........   Passed    0.08 sec
221: static call targets: default subgroup 8, ComputeTarget(32) subgroup 8
221: static call execution passed: 5 variants, 10 dispatches, 480 lanes, 1920 output comparisons
4/7 Test #221: agc_driver_scalar_static_call ....   Passed    0.08 sec
5/7 Test #222: agc_driver_scalar_swappc .........   Passed    0.07 sec
6/7 Test #283: scalar_swappc ....................   Passed    0.00 sec
7/7 Test #286: scalar_call ......................   Passed    0.00 sec
100% tests passed out of 7

GitHub CI, with each runnable test repeated three times:

Platform CTest entries Passed Skipped Failed
linux 469 445 24 0
windows 460 282 178 0

Skipped entries are included in CTest totals above. Windows skipped the Vulkan execution fixtures; Linux ran the bounded-call fixture.

Captured module SHA-256 values (fixture order above, wave32 then wave64):

Module Bytes SHA-256 spirv-val
static-call-module-0.spv 3840 7ea252e211d6859811feb16bd9278cd4ba2a5c03514d4696d7b5488ca060d951 Pass
static-call-module-1.spv 3840 239c4683127d3c1e78b0a0592f62a5d6014a41aabd6ad2613e5a63e77a0f1fec Pass
static-call-module-2.spv 3840 7ea252e211d6859811feb16bd9278cd4ba2a5c03514d4696d7b5488ca060d951 Pass
static-call-module-3.spv 3840 239c4683127d3c1e78b0a0592f62a5d6014a41aabd6ad2613e5a63e77a0f1fec Pass
static-call-module-4.spv 3856 b92b4d0fdea18b814f223db55962719ade17963ef993aa6d7b1207f459238dfa Pass
static-call-module-5.spv 3856 83511cf385a7e2aa962e81c6a35c85eacb3529eba762e8469e6f8a60e38be3e1 Pass
static-call-module-6.spv 3824 4d03bc103aa8a145d479bab7163ea93cf977e06a8f29d2be9a5cd8fbdff5f6c3 Pass
static-call-module-7.spv 3824 0874f20b5e983101c0155688210d16ba569ed205e8d0c66b3d5bee6acc848725 Pass
static-call-module-8.spv 3888 460d00fe5881bbde6de9c898388d8f2bb39e958e3154d0c567181a8c34712e72 Pass
static-call-module-9.spv 3888 8d2bb16c644839e40f93e33982c12e9350103d1a118086d1bf3f7329d3345183 Pass

The capture copy changes only file output and the optional expected-bit negative control; it links the same CMake-built compiler and driver. Native RDNA2/PS5 validation is absent.

Checklist

  • Based on current main; both former prerequisites are merged, and only the three follow-up commits remain; neither open PR implements this bounded CALL execution follow-up
  • One topic per PR; follow-up work is in a new PR
  • No comments added to code
  • Unsupported paths throw; known limits are recorded in TechnicalDebt
  • No investigation notes, reports, logs or images added to the repository
  • No new third-party dependency
  • Generic behaviour, not specific to one title
  • Depends on: none; feat(shader): translate s_swappc_b64 fetch-shader calls at vertex entry #1217 and feat(shader): implement S_CALL_B64 #895 are merged
  • AI-assisted: yes

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 8, 2026
@Sanjaysrinivas
Sanjaysrinivas force-pushed the feat/shader-static-call branch from b1b432b to 3e14f46 Compare October 8, 2026 11:48
@Sanjaysrinivas
Sanjaysrinivas marked this pull request as ready for review October 8, 2026 11:48
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 8, 2026
@Sanjaysrinivas
Sanjaysrinivas force-pushed the feat/shader-static-call branch from 3e14f46 to 6214d25 Compare October 8, 2026 12:23
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 8, 2026
@Sanjaysrinivas
Sanjaysrinivas force-pushed the feat/shader-static-call branch from 6214d25 to 2a3616e Compare October 8, 2026 14:42
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 8, 2026
@Sanjaysrinivas
Sanjaysrinivas force-pushed the feat/shader-static-call branch from 2a3616e to 871e631 Compare October 8, 2026 15:18
@github-actions github-actions Bot added build shader core/shader docs agc libSceAgc* labels Oct 8, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 8, 2026
Reuse the prerequisite call-region analysis for signed relative calls, including callees after s_endpgm. Lower the saved link as shader base plus PC+4 and reject unsupported targets and call shapes.

Add decoder, IR, CFG and Vulkan execution regressions. Seven focused CMake tests pass; five layouts cover 15 dispatches and 3,200 output comparisons on lavapipe. Record ISA sources and native RDNA2/PS5 limits in TechnicalDebt.
Reject tuple and descriptor overlap, wrong-pair returns, relative SGPR reads, reversed regions and ordinary or conditional fall-through into a callee. Preserve the existing scalar write analysis and require exact return-pair matching.

Seven targeted regressions fail before these guards and pass afterward. Seven focused CMake tests and 15 SPIR-V validations pass; the execution matrix passes 3,200 comparisons and detects a one-bit expected-output mutation.
ComputeTarget(32) can fall back to the default subgroup size. Report both effective sizes and dispatch the third configuration only when it differs, so duplicate runs do not imply wave32 host coverage.

Lavapipe exposes subgroup 8 for both requests: seven CMake checks pass, the distinct matrix passes 1,920 comparisons, ten captured modules validate, and the expected-output negative control fails as intended.
@Sanjaysrinivas
Sanjaysrinivas force-pushed the feat/shader-static-call branch from 871e631 to 2b2a0c0 Compare October 8, 2026 18:29
@adriadam10

Copy link
Copy Markdown
Collaborator

Reviewed on the merge with current main (after #895, #639 and #1217). I'd made the same adaptations in parallel: the s_call_b64 refusal removed now that calls execute, and raw bounds (0x31016fac) in ScalarStaticCall.cpp. Your rebased head matches them line for line (only the TechnicalDebt line order differs). Tested: the 8 call/swappc/control-flow tests pass on RADV (gfx1035) and CI's lavapipe, and the full ctest passes (485/485). Will merge once Linux CI is green.

@adriadam10
adriadam10 merged commit 406b5e1 into boykopovar:main Oct 8, 2026
6 of 7 checks passed
@Sanjaysrinivas
Sanjaysrinivas deleted the feat/shader-static-call branch October 8, 2026 21:44
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.

2 participants