Skip to content

AGC: each 32-bit BDA store emits five write lookups, so agc_driver_flat_store compiles to 134K/269K GEN instructions and takes ~150 s cold on Intel #1256

Closed

Description

@kr4y4

agc_driver_flat_store runs a 45-dword program and takes about 150 s on an Intel iGPU, almost all of it creating its compute pipelines, so it exceeds the 120 s ctest timeout whenever the driver's shader cache is cold. It isn't a hang: the same run takes 0.8 s once the driver has cached the pipelines. The size comes from the BDA store emission.

Measured with APS5_PIPELINE_STATS=1 on main at 4764e59, Windows 11:

Device Program Size of the compiled shader Cold pipeline creation (whole test) Warm
Intel Raptor Lake-S, driver 32.0.101.6790 wave32 134,185 GEN instructions, 10,437 basic blocks, 83 loops ~150 s 0.8 s
Intel Raptor Lake-S, driver 32.0.101.6790 wave64 268,907 GEN instructions, 20,870 basic blocks, 166 loops
RTX 4070 Laptop, driver 32.0.15.9597 wave32 / wave64 490,496 / 930,048 bytes of binary 27 s 3.2 s

The cold timings were taken with ANYPS5_NO_SHADER_CACHE=1, plus a fresh APS5_LOOP_GUARD value to miss the driver caches. The APS5_PIPELINE_STATS run without the guard also missed the Intel cache and took 150 s.

Where the size comes from

After translation the program has 15 StoreAddressU32, one StoreAddressU16, one StoreAddressU8 and one LoadAddressU32 (APS5_DUMP_IR).

For each 32-bit store, EmitBdaStore (SpirvBdaRead.cpp) emits:

  • the aligned EmitBdaStoreAt;
  • under the unaligned branch, four byte EmitBdaStoreAt.

Each of those calls get_bda_write_pointer and note_bda_write:

  • get_bda_write_pointer (DefineBdaLookup) contains a loop.
  • note_bda_write unrolls WrittenPageProbes = 8 atomic compare-exchange probes.

For this program that is 15 × 5 + 2 + 1 = 78 write lookups. The Intel wave32 executable reports 83 loops, which suggests the driver inlines the calls.

Reads already avoid the same expansion: EmitBdaDwordReads probes the span once instead of four lookups per dword (the comment there cites a Bink pass).

Possible directions (no PR, because the order in which faults are recorded has to stay exactly as it is):

  • Probe the 4-byte span of a 32-bit store once, and take the byte path only when the probe rejects it, as the reads do.
  • Keep the unaligned byte path in a loop, so each store emits one byte lookup instead of four.

Found with AI assistance (Claude Code); the numbers above were measured on the hardware listed.

Activity

  1. hashaam1217 commented on Oct 8, 2026

    @hashaam1217
    Contributor

    Linux numbers for this issue and the open fixes. Intel Iris Xe (Alder Lake GT2), Mesa 25.2.8 ANV, kernel 7.0, each PR applied to 2cc5735. Cold runs with ANYPS5_NO_SHADER_CACHE=1 MESA_SHADER_CACHE_DISABLE=true APS5_PIPELINE_STATS=1 APS5_PROFILE_DRAW=1, one test at a time in a cgroup limited to 7 GiB. Peak RSS is from GNU time. On Linux the cost is memory as well as time: on main, ANV's compile of flat_store reaches 7.04 GiB.

    agc_driver_flat_store (no spills in any column):

    main #1265 #1280 #1282
    Result passes, at the 7 GiB limit passes passes killed by the 7 GiB limit
    Wall time 84.4 s 27.0 s 31.1 s killed after 72 s
    Peak RSS 7.04 GiB 1.81 GiB 2.11 GiB 7.05 GiB when killed
    First pipeline: SPIR-V words → GEN instructions, compile time 10,897 → 55,929, 17.1 s 7,561 → 26,393, 5.7 s 7,598 → 27,965, 6.5 s 8,243 → 54,375, 19.5 s
    Second pipeline 17,450 → 111,860, 66.9 s 11,081 → 50,309, 21.1 s 11,118 → 53,453, 24.5 s killed while compiling

    The other store-heavy tests follow the same pattern. The table gives GEN instructions per pipeline; both tests fail on this device for the subgroup reason in #1185:

    main #1265 #1280 #1282
    global_store_addtid 20,550 8,725 9,289 19,854
    global_vcc_base 33,525 / 67,050, peak 2.73 GiB 21,708 / 43,416, peak 1.40 GiB 22,326 / 44,652, peak 1.50 GiB 32,891 / 65,782, peak 2.78 GiB

    Why #1282 loses #1265's gain on Mesa. #1282 contains #1265 and #1280, which measure about the same, so the difference comes from its last commit (e63cc30). That commit adds a 4-byte probe whose fallback write_bda_dword_bytes (and note_bda_write) is marked DontInline. Mesa inlines every function in a Vulkan shader regardless:

    So on Linux the shared fallback is copied back into every store site. #1282's SPIR-V is smaller than main's, but the compiled shader is not, and the same applies to AMD GPUs on RADV.

    With #1185. #1185 makes ANV compile these at SIMD32, where main's shaders spill heavily (numbers in my comment there; flat_store and global_vcc_base were killed at a 5 GiB limit). With #1185 and #1265 together, all three pass:

    #1185 + #1265, SIMD32 Wall time Peak RSS Largest pipeline
    flat_store 95.6 s 3.91 GiB 11,081 words → 290,685 GEN instructions, 13,297 spills, 85.6 s
    global_store_addtid 6.2 s (41.7 s with #1185 alone) 0.55 GiB (2.49 GiB alone) 6,882 words → 44,502 instructions
    global_vcc_base 37.0 s 1.60 GiB (killed at 5 GiB alone) 8,657 words → 109,215 instructions

    The full Intel suite with both has 14 failures, against 16 with #1185 alone: flat_store and global_vcc_base now pass. The remaining 14 are:

    NVIDIA RTX 3060 (595.91.07): full suite 402/402 with main, #1265, #1282, and #1185 + #1265.

  2. kr4y4 commented on Oct 8, 2026

    @kr4y4
    ContributorAuthor

    Windows numbers for the same three PRs, to go with the Linux ones above. Windows 11, Intel Raptor Lake-S (driver 32.0.101.6790) and RTX 4070 Laptop (driver 32.0.15.9597). All three heads are on main c5e1018: #1265 at 2e60f55, #1280 at 1dca7b4, #1282 at e63cc30. Settings: ANYPS5_NO_SHADER_CACHE=1 APS5_PIPELINE_STATS=1, one test at a time.

    The Intel driver cache can't be cleared here because the directory is locked. The PR variants emit new SPIR-V, so their times are cold compiles. main's flat_store was served from the driver cache in this run (1.8 s); its cold time, 150 s, is the one in the description.

    agc_driver_flat_store, wave32 / wave64 pipelines:

    Intel GEN instructions Intel wall time NVIDIA binary bytes
    main 134,185 / 268,907 150 s cold (cached here) 490,496 / 930,048
    #1265 50,123 / 100,715 42.3 s 77,696 / 129,280
    #1280 51,634 / 104,334 39.0 s 84,352 / 144,000
    #1282 99,443 / 202,093 195.1 s 68,608 / 95,616

    Intel GEN instructions, other store-heavy tests (wave32 / wave64; global_vcc_base has two programs):

    global_store_addtid global_vcc_base
    main 46,738 / 94,395 74,466 / 161,317 and 21,319 / 42,684
    #1265 17,583 / 35,139 45,479 / 91,111 and 15,682 / 23,325
    #1280 18,148 / 36,455 46,081 / 91,967 and 16,004 / 23,706
    #1282 37,713 / 76,208 65,340 / 139,693 and 18,400 / 36,835

    The Windows Intel driver behaves like ANV:

    So of the three, #1265 is the one that shrinks the compiled shader on both Intel drivers. All variants pass the three tests on the Intel GPU here, and flat_store on NVIDIA.

    Measured with AI assistance (Claude Code) on the hardware listed.

  3. klosixgaming-arch commented on Oct 8, 2026

    @klosixgaming-arch
    Contributor

    This is a significant performance issue with BDA store emission. Based on my review of recent PRs related to the shader recompiler (including #1523 for memory amplification and quadratic scan fixes), I can provide some context:

    The analysis showing that each 32-bit BDA store emits five write lookups is compelling. The comparison to how reads already avoid this expansion (with EmitBdaDwordReads probing the span once) suggests a clear path forward for fixing this issue.

    Some observations based on similar issues I've seen:

    1. The fact that warm runs are fast (0.8 s vs 150 s cold) confirms this is a compilation issue, not a runtime performance issue
    2. The proposed direction of probing the 4-byte span once and taking the byte path only when rejected seems reasonable
    3. This could potentially be addressed in a similar way to how fix(shader): memory amplification in ShaderDiskCache and quadratic scans in GraphBuilder and InstructionTranslator #1523 fixed quadratic scans in the shader recompiler

    I'd suggest prioritizing this fix, as it affects compilation time for any program that uses BDA stores, which is likely many games.

  4. LeandroLP23 commented on Oct 8, 2026

    @LeandroLP23
    Contributor

    AMD numbers for the same tests and the current heads of the three PRs: #1265 at d3cdffa, #1280 at 1dca7b4 and #1282 at fe567b5 (the commit that drops DontInline from note_bda_write), each merged into main 1081602. Radeon 860M (RDNA 3.5), Windows driver 32.0.13058.8001, Windows 11, MinGW-w64 GCC 15.2.0, Release. Settings ANYPS5_NO_SHADER_CACHE=1 APS5_PIPELINE_STATS=1, one test at a time.

    The AMD driver caches pipelines per executable, so each run used a fresh copy of the test binary under a new name: 0.2 s warm, the times below are cold. The AMD driver reports no instruction count through pipelineExecutableInfo, only registers and scratch; as a rough size measure, the table gives the driver's cache file for the run, which grows in 64 KiB steps. Wall time and peak working set are for the whole test, median of 3 cold runs.

    flat_store global_store_addtid global_vcc_base
    main 3.9 s, 0.46 GiB, 128 KiB, 28 VGPRs 4.4 s, 0.41 GiB, 128 KiB 9.0 s, 0.52 GiB, 256 KiB
    #1265 3.4 s, 0.49 GiB, 128 KiB, 26 VGPRs 4.0 s, 0.43 GiB, 128 KiB 9.1 s, 0.53 GiB, 256 KiB
    #1280 3.9 s, 0.49 GiB, 128 KiB, 26 VGPRs 4.2 s, 0.43 GiB, 128 KiB 8.9 s, 0.54 GiB, 256 KiB
    #1282 3.2 s, 0.47 GiB, 64 KiB, 25 VGPRs 3.7 s, 0.41 GiB, 128 KiB 8.2 s, 0.51 GiB, 256 KiB

    No scratch in any pipeline. So the AMD Windows driver compiles all four variants in seconds and well under 1 GiB; the BDA store size matters on Intel (and Mesa), not here. The only visible difference is flat_store's cache file, half the size with #1282, in the same direction as NVIDIA's binary sizes above.

    Full ctest on this GPU: main, #1265 and #1280 pass 444/445 (the failure is #825). #1282 also fails agc_shader_memory; details in #1282.

    Measured with AI assistance (Claude Code) on the hardware listed.

  5. oneandonlydean commented on Oct 10, 2026

    @oneandonlydean
    Collaborator

    Fixed by #1265: the unaligned byte path of a BDA store is now a loop with one write lookup, and the written-page probes are a loop instead of eight unrolled CAS probes (#1280 also loads the slot before the CAS). On the Intel numbers above that takes flat_store from ~150 s to ~42 s cold, under the ctest timeout. Reopen if it still times out on current main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions