Skip to content

PRAGMATA: captured wave64 pixel shader still fails lane 63 with #6180 #6582

Closed

Description

@Ondra97

Summary

PRAGMATA (version 01.200.000) still stops during shader preparation with a wave64 pixel lane-63 refusal after applying #6180. I captured the failing compiler request and reproduced the same failure with the CPU-only agc_shader_replay tool, without running the title.

Related: #5398 and #6180. This is an additional failing capture from PRAGMATA; I have not established whether it exhausts the 2048-step proof budget or fails a different proof condition. I am not claiming that #6180 introduced the failure.

Tested tree and environment

Observed result

One visible game run with a fresh isolated shader-cache directory ended after about 41 seconds with exit 0xC0000409 / SIGABRT, before reaching the menu. No frame dump was produced. The relevant error was:

shader 0x211190d400 stage 5 (shader_211190d400.req): ShaderRecompiler::Recompile: SPIR-V emission failed: hash=0x142028624896 stage=2 opcode=ReadLane reason=v_readlane_b32 of lane 63 is outside the 32-lane host subgroup that runs this wave64 program at one lane per invocation

The separate replay of the captured request exits with code 1:

shader_211190d400.req: 9536 code words, 4 user data, 2 memory regions, wave64
  recompile failed: ShaderRecompiler::Recompile: SPIR-V emission failed: hash=0x142028624896 stage=2 opcode=ReadLane reason=v_readlane_b32 of lane 63 is outside the 32-lane host subgroup that runs this wave64 program at one lane per invocation

Validation

The full Windows build passed. These six CTest registrations passed, with no failures or skips:

The GPU tests ran on the RTX 4070 Ti SUPER with ANYPS5_REQUIRE_VULKAN=1 and ANYPS5_NO_SHADER_CACHE=1. The import audit of the converted title reported zero absent imports and zero provider mismatches. The failing PRAGMATA capture above is separate from the passing NHL 26 regression test.

Not tested: this capture on the same base without #6180, Linux, other GPUs, or pixel-output correctness. The earlier title run on main 4808d9c2 plus #5776 also failed with this error class, but I have not established that it was the same shader.

Local standalone replay evidence

I retained the complete serialized request locally and reproduced the failure with the existing CPU-only replay tool:

ANYPS5_NO_SHADER_CACHE=1 agc_shader_replay shader_211190d400.req

On Windows, the unpatched library directory was prepended to PATH as documented. The replay reported 9536 code words, 4 user-data entries, 2 memory regions, wave64, and exited with code 1 with the same lane-63 refusal. It does not require running the game or executing a GPU pipeline.

The full captured request is 104,024 bytes; SHA-256:

a9f1f0318f46c97929ccebfffcfd8d9bebc8cbcfab132cabd4369bcc52f4a82e

The captured shader payload is retained locally and is not included in this public report. The cause of the remaining refusal needs investigation; I have not tested a larger budget or a changed proof algorithm.

AI-assisted testing and report: yes (Codex).

Activity

  1. changed the title [-]PRAGMATA (PPSA02530): captured wave64 pixel shader still fails lane 63 with #6180[/-] [+]PRAGMATA: captured wave64 pixel shader still fails lane 63 with #6180[/+] on Oct 15, 2026
  2. MarcZX10 commented on Oct 15, 2026

    @MarcZX10
    Contributor

    I checked the currently open work before considering a separate change. The report says the captured request is retained locally and does not establish whether the 2048-step budget is exhausted or another proof condition refuses it, so I cannot yet identify a safe fix or a regression input from the public details alone. Could you share the replay’s proof termination reason and counters (especially whether it reaches the budget, and the last lane/state being analyzed), or a small synthetic request that reproduces the same condition without title data? That would distinguish a budget increase from a separate proof-logic issue.

  3. Ondra97 commented on Oct 15, 2026

    @Ondra97
    ContributorAuthor

    Thanks, I measured the proof termination locally without publishing the captured request.

    Tested tree: main 44df21632125dbe3113a19173e7ebb879da53003 + #6180 99500f9d4508e1d8bed7222e204c0db8d0eb4c7f, with the same test-only #6452 prerequisite recorded in the issue. Windows 11 Pro 26300, WinLibs GCC 15.2 r7, Release. This was CPU-only agc_shader_replay with shader caching disabled and the same request SHA-256 as the issue (a9f1f0318f46c97929ccebfffcfd8d9bebc8cbcfab132cabd4369bcc52f4a82e).

    I temporarily instrumented EntryLaneWalk::Spend and Incoming, preserving the existing checks, short-circuit order and active-Phi cycle handling. A state was counted as (IR value, proof kind, mask half); distinct counts include attempted calls rejected after the budget reached zero. Phi cycle revisits count active-stack hits, not completed-proof cache hits.

    The observed walks exhaust the budget. There are four walks: lane 31 wide/narrow, then lane 63 wide/narrow. Within each budget variant, all four produced the same counters:

    Initial budget Steps consumed Spend attempts Budget refusals Distinct nodes / states Phi expansions Active-Phi revisits Maximum active Phis
    2,048 (#6180) 2,048 2,055 7 58 / 66 292 524 16
    16,384, diagnostic only 16,384 16,395 11 61 / 70 2,505 4,134 19
    65,536, diagnostic only 65,536 65,543 7 62 / 68 10,492 17,813 20

    Each attempt returned false with zero budget remaining. The root in this instrumented IR was ZeroBit, node 62168, opcode LogicalAnd.

    For the requested last lane/state details (the same state details occurred in the lane-31 and lane-63, wide and narrow attempts):

    • At 2,048, the first refused Spend was Word, mask half 1, node 229516, opcode Phi.
    • At 16,384, it was Word, half 0, node 15779, opcode BitwiseAnd32.
    • At 65,536, it was ZeroBit, node 229122, opcode Phi.
    • The last attempted state after unwinding was ZeroBit, node 62166, opcode INotEqual32 in all three variants. The lane-63 narrow attempt then ends with the reported emission refusal.

    All three complete replays exited with code 1 and the same lane-63 message. This demonstrates budget exhaustion in these attempts; it does not establish whether an unbounded/completed proof would accept the shader, nor does it validate a caching algorithm or pixel output. The repeated traversal of a small set of states appears relevant to the completed-proof-cache follow-up discussed on #6180, but I have not tested that implementation.

    I restored the original source byte for byte and rebuilt the original replay and shader-memory test executable. agc_shader_memory and agc_shader_entry_lane_pixel_replay still pass (2/2, no skips); the restored PRAGMATA replay reproduces the original refusal. No higher budget or diagnostic code was installed in the game runtime, and no new game run was performed for these measurements.

    AI-assisted diagnostics and report: yes (Codex).

  4. MarcZX10 commented on Oct 15, 2026

    @MarcZX10
    Contributor

    Thanks, this answers the termination question: all four walks hit the 2,048-step budget, so #6180’s cap is the immediate refusal. The measurements do not yet show that a larger budget completes the proof; even 65,536 steps still exhausts it, while the walks visit only about 60 distinct nodes/states and repeatedly expand Phi nodes. That makes a completed-proof cache a plausible separate follow-up, but it needs to demonstrate that the captured replay completes and remains correct around active Phi cycles. I’ll keep that separate from the focused budget change in #6180.

  5. cookie42073-cmd commented on Oct 15, 2026

    @cookie42073-cmd
    Contributor

    Hey @Ondra97, those counters are useful. The completed-proof-cache candidate discussed here is already available in this patch and test-log package, with a later same-base comparison against #6180. It keeps a 512-step budget and avoids retaining provisional results from active Phi cycles.

    Would you be willing to try that candidate in CPU-only replay against the request you have retained (a9f1f0318f46c97929ccebfffcfd8d9bebc8cbcfab132cabd4369bcc52f4a82e)? Please keep the candidate's 512-step budget, without the diagnostic budget increases or other proof changes. The useful result would be the exact tested revision/applied diff, success or the remaining refusal, and the proof counters if available. No need to publish the captured payload or run the game for this check.

    The attached patch predates your base and #6180, so it may need a mechanical rebase; any adaptation should be included in the tested diff. If it does not apply cleanly, let me know rather than resolving non-mechanical conflicts for this test. Its earlier passing NHL26 capture and synthetic loop results do not establish that it handles this PRAGMATA capture. I'll keep the investigation separate from #6180, as Marc requested.

    AI-assisted investigation and write-up, independently reviewed.

  6. Ondra97 commented on Oct 16, 2026

    @Ondra97
    ContributorAuthor

    Thanks, I tried your 9456e863e4cd4bb88ac77aa6871e86ad23a7349f candidate from the linked package. It passes the supplied CPU controls, but this retained PRAGMATA request still fails at the 512-step budget.

    Exact tested sources

    Windows 11 Pro 26300, WinLibs MinGW GCC 15.2 r7, Release, ANYPS5_NO_SHADER_CACHE=1. I explicitly rebuilt both agc_shader_replay and agc_shader_memory_tests in each phase. The baseline and candidate used identical test additions. The retained request is unchanged: SHA-256 a9f1f0318f46c97929ccebfffcfd8d9bebc8cbcfab132cabd4369bcc52f4a82e.

    CPU-only before / after

    Compiler, budget 512 in both Retained PRAGMATA replay agc_shader_entry_lane_pixel_replay (NHL26) agc_shader_memory, including the added loop/negative cases
    Original proof, candidate test additions only Fails, exit 1, lane-63 refusal Fails Fails
    Exact completed-proof-cache candidate Fails, exit 1, same lane-63 refusal Pass Pass

    The candidate's selected CTest run was 2/2 passed, zero skips. The uninstrumented PRAGMATA replay still reports:

    9536 code words, 4 user data, 2 memory regions, wave64
    recompile failed: ShaderRecompiler::Recompile: SPIR-V emission failed: hash=0x142028624896 stage=2 opcode=ReadLane reason=v_readlane_b32 of lane 63 is outside the 32-lane host subgroup that runs this wave64 program at one lane per invocation
    

    Separate counters-only build of the candidate

    I then added temporary counters without changing its proof decisions or its 512 budget. All four walks (lane 31 and 63, wide and narrow) returned false with identical counters:

    Metric Per walk
    Steps spent / remaining 512 / 0
    prove calls 539
    Cache hits / retained entries 21 / 18
    Spend attempts / budget refusals 518 / 6
    Distinct attempted IR nodes / proof states 58 / 64
    Successful checks not retained by the existing provisional-cycle guard 470
    Phi expansions / active-Phi revisits 88 / 164
    Maximum active Phis 16

    The first budget refusal was NotHelper, node 63793, opcode LogicalAnd. The last attempted state was ZeroBit, node 62166, opcode INotEqual32. State counts include attempted calls refused after exhaustion; the 470 count means successful checks for which the candidate's existing cache-retention condition was false. I did not relax that guard or test another proof algorithm.

    The uninstrumented result and these counters confirm that the candidate was exercised, including cache hits, but did not complete this capture's proof within 512 steps.

    Exact applied production diff on the tested base, after reverting the #6180 budget increase

    The test-file additions are unchanged from your public patch package. This is only the applied C++ production diff; it contains no title capture data.

    --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp
    +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp
    @@ -317,22 +317,37 @@
     }
     
     class EntryLaneWalk {
    +    enum class Proof : std::size_t { ZeroBit, NotHelper, LowWord, HighWord, LowWord64, HighWord64 };
    +
     public:
         explicit EntryLaneWalk(IrShaderStage stage) : everyInvocationEnters(EveryInvocationEnters(stage)) {}
     
         bool ZeroBit(const IrValue* bit) {
    -        bit = Resolved(bit);
    -        if (bit == nullptr || !Spend()) return false;
    -        if (bit->HasImmediate()) return bit->Type() == IrType::Bool && !bit->ImmediateBool();
    -        if (Is(bit, IrOpcode::LogicalAnd)) return HelperOrFalseConjunct(bit) || ZeroBit(bit->Argument(0)) || ZeroBit(bit->Argument(1));
    -        if (Is(bit, IrOpcode::SelectU1)) return ZeroBit(bit->Argument(1)) && ZeroBit(bit->Argument(2));
    -        if (Is(bit, IrOpcode::Phi)) return Incoming(bit, [this](const IrValue* incoming) { return ZeroBit(incoming); });
    -        if (Is(bit, IrOpcode::IEqual32)) return HelperTest(bit);
    -        const auto words = LaneBitWords(bit);
    -        return words && Word((*words)[0], 0u) && Word((*words)[1], 1u);
    +        return prove(bit, Proof::ZeroBit, [this](const IrValue* value) {
    +            if (value->HasImmediate()) return value->Type() == IrType::Bool && !value->ImmediateBool();
    +            if (Is(value, IrOpcode::LogicalAnd)) return HelperOrFalseConjunct(value) || ZeroBit(value->Argument(0)) || ZeroBit(value->Argument(1));
    +            if (Is(value, IrOpcode::SelectU1)) return ZeroBit(value->Argument(1)) && ZeroBit(value->Argument(2));
    +            if (Is(value, IrOpcode::Phi)) return Incoming(value, [this](const IrValue* incoming) { return ZeroBit(incoming); });
    +            if (Is(value, IrOpcode::IEqual32)) return HelperTest(value);
    +            const auto words = LaneBitWords(value);
    +            return words && Word((*words)[0], 0u) && Word((*words)[1], 1u);
    +        });
         }
     
     private:
    +    template<typename TCheck>
    +    bool prove(const IrValue* value, Proof proof, TCheck&& check) {
    +        value = Resolved(value);
    +        if (value == nullptr) return false;
    +        auto& cache = proven[static_cast<std::size_t>(proof)];
    +        if (cache.contains(value)) return true;
    +        if (!Spend()) return false;
    +        const auto generation = cycleGeneration;
    +        const bool result = check(value);
    +        if (result && (phis.empty() || generation == cycleGeneration)) cache.insert(value);
    +        return result;
    +    }
    +
         bool Spend() {
             if (budget == 0u) return false;
             --budget;
    @@ -340,47 +355,50 @@
         }
     
         bool NotHelper(const IrValue* predicate) {
    -        predicate = Resolved(predicate);
    -        if (predicate == nullptr || !Spend()) return false;
    -        if (predicate->HasImmediate()) return everyInvocationEnters && predicate->Type() == IrType::Bool && predicate->ImmediateBool();
    -        switch (predicate->Opcode()) {
    -            case IrOpcode::LogicalAnd: return NotHelper(predicate->Argument(0)) || NotHelper(predicate->Argument(1));
    -            case IrOpcode::IEqual32: return HelperTest(predicate);
    -            default: return ZeroBit(predicate);
    -        }
    +        return prove(predicate, Proof::NotHelper, [this](const IrValue* value) {
    +            if (value->HasImmediate()) return everyInvocationEnters && value->Type() == IrType::Bool && value->ImmediateBool();
    +            switch (value->Opcode()) {
    +                case IrOpcode::LogicalAnd: return NotHelper(value->Argument(0)) || NotHelper(value->Argument(1));
    +                case IrOpcode::IEqual32: return HelperTest(value);
    +                default: return ZeroBit(value);
    +            }
    +        });
         }
     
         bool Word(const IrValue* word, std::uint32_t half) {
    -        word = Resolved(word);
    -        if (word == nullptr || !Spend()) return false;
    -        if (word->HasImmediate()) return IsU32(word, 0u);
    -        switch (word->Opcode()) {
    -            case IrOpcode::CompositeExtractU32x4: {
    -                const auto* ballot = Resolved(word->Argument(0));
    -                return IsU32(word->Argument(1), half) && Is(ballot, IrOpcode::Ballot) && NotHelper(ballot->Argument(0));
    +        return prove(word, half == 0u ? Proof::LowWord : Proof::HighWord, [this, half](const IrValue* value) {
    +            if (value->HasImmediate()) return IsU32(value, 0u);
    +            switch (value->Opcode()) {
    +                case IrOpcode::CompositeExtractU32x4: {
    +                    const auto* ballot = Resolved(value->Argument(0));
    +                    return IsU32(value->Argument(1), half) && Is(ballot, IrOpcode::Ballot) && NotHelper(ballot->Argument(0));
    +                }
    +                case IrOpcode::CompositeExtractU64: return IsU32(value->Argument(1), half) && Word64(value->Argument(0), half);
    +                case IrOpcode::BitwiseAnd32: return Word(value->Argument(0), half) || Word(value->Argument(1), half);
    +                case IrOpcode::BitwiseOr32: return Word(value->Argument(0), half) && Word(value->Argument(1), half);
    +                case IrOpcode::Phi: return Incoming(value, [this, half](const IrValue* incoming) { return Word(incoming, half); });
    +                default: return false;
                 }
    -            case IrOpcode::CompositeExtractU64: return IsU32(word->Argument(1), half) && Word64(word->Argument(0), half);
    -            case IrOpcode::BitwiseAnd32: return Word(word->Argument(0), half) || Word(word->Argument(1), half);
    -            case IrOpcode::BitwiseOr32: return Word(word->Argument(0), half) && Word(word->Argument(1), half);
    -            case IrOpcode::Phi: return Incoming(word, [this, half](const IrValue* incoming) { return Word(incoming, half); });
    -            default: return false;
    -        }
    +        });
         }
     
         bool Word64(const IrValue* mask, std::uint32_t half) {
    -        mask = Resolved(mask);
    -        if (mask == nullptr || !Spend()) return false;
    -        if (mask->HasImmediate()) return mask->Type() == IrType::U64 && static_cast<std::uint32_t>(mask->ImmediateU64() >> (32u * half)) == 0u;
    -        switch (mask->Opcode()) {
    -            case IrOpcode::CompositeConstructU64: return Word(mask->Argument(half), half);
    -            case IrOpcode::Phi: return Incoming(mask, [this, half](const IrValue* incoming) { return Word64(incoming, half); });
    -            default: return false;
    -        }
    +        return prove(mask, half == 0u ? Proof::LowWord64 : Proof::HighWord64, [this, half](const IrValue* value) {
    +            if (value->HasImmediate()) return value->Type() == IrType::U64 && static_cast<std::uint32_t>(value->ImmediateU64() >> (32u * half)) == 0u;
    +            switch (value->Opcode()) {
    +                case IrOpcode::CompositeConstructU64: return Word(value->Argument(half), half);
    +                case IrOpcode::Phi: return Incoming(value, [this, half](const IrValue* incoming) { return Word64(incoming, half); });
    +                default: return false;
    +            }
    +        });
         }
     
         template<typename TCheck>
         bool Incoming(const IrValue* phi, TCheck&& check) {
    -        if (!phis.insert(phi).second) return true;
    +        if (!phis.insert(phi).second) {
    +            ++cycleGeneration;
    +            return true;
    +        }
             bool result = phi->ArgumentCount() != 0u;
             for (std::size_t index = 0; result && index < phi->ArgumentCount(); ++index) result = check(phi->Argument(index));
             phis.erase(phi);
    @@ -389,6 +407,8 @@
     
         bool everyInvocationEnters;
         std::unordered_set<const IrValue*> phis;
    +    std::array<std::unordered_set<const IrValue*>, 6> proven;
    +    std::uint32_t cycleGeneration = 0u;
         std::uint32_t budget = 512u;
     };

    After the comparison I restored all affected source files byte for byte and rebuilt the original CPU tools. The two original CPU controls pass again; the original PRAGMATA refusal is reproduced. The installed game runtime was not modified. No game, GPU, Linux, or full application build was run for this candidate check; ANYPS5_ENABLE_SPIRV_TOOLS was OFF, so I am not claiming SPIR-V validation or image correctness.

    The request payload remains private. AI-assisted testing and report: yes (Codex).

  7. cookie42073-cmd commented on Oct 16, 2026

    @cookie42073-cmd
    Contributor

    Thanks @Ondra97 for running the exact patch and recording the before/after results. That confirms the cache is being used while the PRAGMATA request still runs out of steps.

    Could you try one more CPU-only diagnostic: keep the same cached-proof implementation, capture, base and test additions, and change only EntryLaneWalk's budget from 512 to 2,048? Please leave the cycle guard and all proof decisions unchanged. The original uncached 2,048 run failed, but this cached-plus-2,048 comparison hasn't been reported yet.

    Please record the exact tested diff, replay exit/result, and whether it completes, reaches a different refusal, or exhausts the budget again. Please rerun the same two CPU controls, including the helper-inclusive negative cases. The existing step/cache/Phi counters would help if convenient. This is a diagnostic comparison; it doesn't establish a game or rendering fix. Please restore the source and original CPU tools afterward as you did for the previous run.

    I've also linked your result on #6645 so we can coordinate the overlapping work. If this comparison still doesn't finish, a small synthetic reproducer or a reduced proof-dependency graph around the repeated Phi states would let us investigate locally while you keep the captured payload private.

    AI-assisted investigation and write-up, independently reviewed.

  8. Ondra97 commented on Oct 16, 2026

    @Ondra97
    ContributorAuthor

    Thanks, I ran the requested cached proof at 2,048 steps. The uninstrumented replay still fails with exit 1 and the same lane-63 refusal. A separate counters-only build confirms that all four walks exhaust 2,048 steps. Both CPU controls pass, including the helper-inclusive and asymmetric-half negative cases in the unchanged candidate test additions.

    Exact comparison

    I kept the same main base 44df21632125dbe3113a19173e7ebb879da53003, retained #6180's 99500f9d NHL26 fixture/test additions and the existing test-only #6452 prerequisite, and reused your exact 9456e863e4cd4bb88ac77aa6871e86ad23a7349f patch sources from the previous 512-step comparison. I verified the three candidate test files byte for byte. The only source change from that uninstrumented candidate is:

    --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp
    +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp
    @@ -409,7 +409,7 @@
         std::unordered_set<const IrValue*> phis;
         std::array<std::unordered_set<const IrValue*>, 6> proven;
         std::uint32_t cycleGeneration = 0u;
    -    std::uint32_t budget = 512u;
    +    std::uint32_t budget = 2048u;
     };
     
     bool EveryLaneBit(const IrValue* bit, bool constantTrue) {

    The cycle guard, cache-retention condition, proof decisions and capture are unchanged. Request SHA-256: a9f1f0318f46c97929ccebfffcfd8d9bebc8cbcfab132cabd4369bcc52f4a82e. Uninstrumented candidate SpirvAluEmitterMath.cpp SHA-256: f2a002a019872b4a39e8d6b7694c2a111ebc905b3775e64e8aec8b9eefc04aeb. Applied production diff SHA-256: 862312e5f728ee3b8d62f99a4e482dc9a4569191770ff64a046b1dbb59fb5e8e; its complete contents are below.

    Windows 11 Pro build 26300, WinLibs MinGW GCC 15.2 r7, Release, ANYPS5_NO_SHADER_CACHE=1. I explicitly rebuilt both agc_shader_replay and agc_shader_memory_tests before replay and CTest.

    CPU check, cached proof at 2,048 Result
    Retained PRAGMATA replay, uninstrumented Exit 1, same lane-63 refusal
    agc_shader_entry_lane_pixel_replay (NHL26) Pass
    agc_shader_memory, with the unchanged loop and negative additions Pass

    Selected CTest result: 2/2 passed, zero skips, 0.30 s. The replay reports:

    9536 code words, 4 user data, 2 memory regions, wave64
    recompile failed: ShaderRecompiler::Recompile: SPIR-V emission failed: hash=0x142028624896 stage=2 opcode=ReadLane reason=v_readlane_b32 of lane 63 is outside the 32-lane host subgroup that runs this wave64 program at one lane per invocation
    

    Counters from a separate instrumentation build

    The following values are per walk and identical for lane 31/63, wide/narrow:

    Metric Cache, 512 (previous run) Cache, 2,048 (this run)
    Proof result false false
    Steps spent / remaining 512 / 0 2048 / 0
    prove calls 539 2113
    Spend attempts / budget refusals 518 / 6 2056 / 8
    Cache hits / retained entries 21 / 18 57 / 18
    Successful checks not retained by the existing guard 470 1998
    Distinct attempted IR nodes / proof states 58 / 64 59 / 67
    Phi expansions / active-Phi revisits 88 / 164 334 / 706
    Maximum active Phis 16 16

    First budget refusal: ZeroBit, node 63789, opcode INotEqual32. Last attempted state: ZeroBit, node 62166, opcode INotEqual32. Attempted-state counts include calls refused after exhaustion. As before, the unretained-success counter means that the existing retention condition was false; I did not relax it.

    Reduced graph around repeated Phi states

    I also recorded structural parent-to-child proof-call edges, without dumping shader words, constants, memory contents or the request. One repeatedly visited fragment consists of four HighWord/Phi states:

    Label IR node Active-Phi revisits Successful checks not retained
    A 229516 203 238
    B 240961 16 49
    C 240962 0 33
    D 230930 40 116

    Observed edges within that fragment, aggregated over one walk:

    A -> B: 36 calls
    D -> A: 76 calls
    B -> D: 33 calls
    B -> C: 34 calls
    C -> D: 34 calls
    

    This shows the observed call cycles A -> B -> D -> A and A -> B -> C -> D -> A. Other incoming/outgoing calls are omitted, and all counts stop at the 2,048-step cutoff. This is a reduced structural trace for further investigation, not a standalone synthetic reproducer or a claim that these states can safely be cached.

    Complete uninstrumented production diff against the original uncached 2,048-step source

    The three candidate test files are unchanged from your public patch package and the previous comparison. This is C++ source only.

    --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp
    +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp
    @@ -317,22 +317,37 @@
     }
     
     class EntryLaneWalk {
    +    enum class Proof : std::size_t { ZeroBit, NotHelper, LowWord, HighWord, LowWord64, HighWord64 };
    +
     public:
         explicit EntryLaneWalk(IrShaderStage stage) : everyInvocationEnters(EveryInvocationEnters(stage)) {}
     
         bool ZeroBit(const IrValue* bit) {
    -        bit = Resolved(bit);
    -        if (bit == nullptr || !Spend()) return false;
    -        if (bit->HasImmediate()) return bit->Type() == IrType::Bool && !bit->ImmediateBool();
    -        if (Is(bit, IrOpcode::LogicalAnd)) return HelperOrFalseConjunct(bit) || ZeroBit(bit->Argument(0)) || ZeroBit(bit->Argument(1));
    -        if (Is(bit, IrOpcode::SelectU1)) return ZeroBit(bit->Argument(1)) && ZeroBit(bit->Argument(2));
    -        if (Is(bit, IrOpcode::Phi)) return Incoming(bit, [this](const IrValue* incoming) { return ZeroBit(incoming); });
    -        if (Is(bit, IrOpcode::IEqual32)) return HelperTest(bit);
    -        const auto words = LaneBitWords(bit);
    -        return words && Word((*words)[0], 0u) && Word((*words)[1], 1u);
    +        return prove(bit, Proof::ZeroBit, [this](const IrValue* value) {
    +            if (value->HasImmediate()) return value->Type() == IrType::Bool && !value->ImmediateBool();
    +            if (Is(value, IrOpcode::LogicalAnd)) return HelperOrFalseConjunct(value) || ZeroBit(value->Argument(0)) || ZeroBit(value->Argument(1));
    +            if (Is(value, IrOpcode::SelectU1)) return ZeroBit(value->Argument(1)) && ZeroBit(value->Argument(2));
    +            if (Is(value, IrOpcode::Phi)) return Incoming(value, [this](const IrValue* incoming) { return ZeroBit(incoming); });
    +            if (Is(value, IrOpcode::IEqual32)) return HelperTest(value);
    +            const auto words = LaneBitWords(value);
    +            return words && Word((*words)[0], 0u) && Word((*words)[1], 1u);
    +        });
         }
     
     private:
    +    template<typename TCheck>
    +    bool prove(const IrValue* value, Proof proof, TCheck&& check) {
    +        value = Resolved(value);
    +        if (value == nullptr) return false;
    +        auto& cache = proven[static_cast<std::size_t>(proof)];
    +        if (cache.contains(value)) return true;
    +        if (!Spend()) return false;
    +        const auto generation = cycleGeneration;
    +        const bool result = check(value);
    +        if (result && (phis.empty() || generation == cycleGeneration)) cache.insert(value);
    +        return result;
    +    }
    +
         bool Spend() {
             if (budget == 0u) return false;
             --budget;
    @@ -340,47 +355,50 @@
         }
     
         bool NotHelper(const IrValue* predicate) {
    -        predicate = Resolved(predicate);
    -        if (predicate == nullptr || !Spend()) return false;
    -        if (predicate->HasImmediate()) return everyInvocationEnters && predicate->Type() == IrType::Bool && predicate->ImmediateBool();
    -        switch (predicate->Opcode()) {
    -            case IrOpcode::LogicalAnd: return NotHelper(predicate->Argument(0)) || NotHelper(predicate->Argument(1));
    -            case IrOpcode::IEqual32: return HelperTest(predicate);
    -            default: return ZeroBit(predicate);
    -        }
    +        return prove(predicate, Proof::NotHelper, [this](const IrValue* value) {
    +            if (value->HasImmediate()) return everyInvocationEnters && value->Type() == IrType::Bool && value->ImmediateBool();
    +            switch (value->Opcode()) {
    +                case IrOpcode::LogicalAnd: return NotHelper(value->Argument(0)) || NotHelper(value->Argument(1));
    +                case IrOpcode::IEqual32: return HelperTest(value);
    +                default: return ZeroBit(value);
    +            }
    +        });
         }
     
         bool Word(const IrValue* word, std::uint32_t half) {
    -        word = Resolved(word);
    -        if (word == nullptr || !Spend()) return false;
    -        if (word->HasImmediate()) return IsU32(word, 0u);
    -        switch (word->Opcode()) {
    -            case IrOpcode::CompositeExtractU32x4: {
    -                const auto* ballot = Resolved(word->Argument(0));
    -                return IsU32(word->Argument(1), half) && Is(ballot, IrOpcode::Ballot) && NotHelper(ballot->Argument(0));
    +        return prove(word, half == 0u ? Proof::LowWord : Proof::HighWord, [this, half](const IrValue* value) {
    +            if (value->HasImmediate()) return IsU32(value, 0u);
    +            switch (value->Opcode()) {
    +                case IrOpcode::CompositeExtractU32x4: {
    +                    const auto* ballot = Resolved(value->Argument(0));
    +                    return IsU32(value->Argument(1), half) && Is(ballot, IrOpcode::Ballot) && NotHelper(ballot->Argument(0));
    +                }
    +                case IrOpcode::CompositeExtractU64: return IsU32(value->Argument(1), half) && Word64(value->Argument(0), half);
    +                case IrOpcode::BitwiseAnd32: return Word(value->Argument(0), half) || Word(value->Argument(1), half);
    +                case IrOpcode::BitwiseOr32: return Word(value->Argument(0), half) && Word(value->Argument(1), half);
    +                case IrOpcode::Phi: return Incoming(value, [this, half](const IrValue* incoming) { return Word(incoming, half); });
    +                default: return false;
                 }
    -            case IrOpcode::CompositeExtractU64: return IsU32(word->Argument(1), half) && Word64(word->Argument(0), half);
    -            case IrOpcode::BitwiseAnd32: return Word(word->Argument(0), half) || Word(word->Argument(1), half);
    -            case IrOpcode::BitwiseOr32: return Word(word->Argument(0), half) && Word(word->Argument(1), half);
    -            case IrOpcode::Phi: return Incoming(word, [this, half](const IrValue* incoming) { return Word(incoming, half); });
    -            default: return false;
    -        }
    +        });
         }
     
         bool Word64(const IrValue* mask, std::uint32_t half) {
    -        mask = Resolved(mask);
    -        if (mask == nullptr || !Spend()) return false;
    -        if (mask->HasImmediate()) return mask->Type() == IrType::U64 && static_cast<std::uint32_t>(mask->ImmediateU64() >> (32u * half)) == 0u;
    -        switch (mask->Opcode()) {
    -            case IrOpcode::CompositeConstructU64: return Word(mask->Argument(half), half);
    -            case IrOpcode::Phi: return Incoming(mask, [this, half](const IrValue* incoming) { return Word64(incoming, half); });
    -            default: return false;
    -        }
    +        return prove(mask, half == 0u ? Proof::LowWord64 : Proof::HighWord64, [this, half](const IrValue* value) {
    +            if (value->HasImmediate()) return value->Type() == IrType::U64 && static_cast<std::uint32_t>(value->ImmediateU64() >> (32u * half)) == 0u;
    +            switch (value->Opcode()) {
    +                case IrOpcode::CompositeConstructU64: return Word(value->Argument(half), half);
    +                case IrOpcode::Phi: return Incoming(value, [this, half](const IrValue* incoming) { return Word64(incoming, half); });
    +                default: return false;
    +            }
    +        });
         }
     
         template<typename TCheck>
         bool Incoming(const IrValue* phi, TCheck&& check) {
    -        if (!phis.insert(phi).second) return true;
    +        if (!phis.insert(phi).second) {
    +            ++cycleGeneration;
    +            return true;
    +        }
             bool result = phi->ArgumentCount() != 0u;
             for (std::size_t index = 0; result && index < phi->ArgumentCount(); ++index) result = check(phi->Argument(index));
             phis.erase(phi);
    @@ -389,6 +407,8 @@
     
         bool everyInvocationEnters;
         std::unordered_set<const IrValue*> phis;
    +    std::array<std::unordered_set<const IrValue*>, 6> proven;
    +    std::uint32_t cycleGeneration = 0u;
         std::uint32_t budget = 2048u;
     };

    Afterward I restored all affected source files byte for byte and verified the original Git diff and prerequisite files against the pre-test snapshot. I explicitly rebuilt the original CPU tools; their two original controls passed again (2/2, zero skips), and the original PRAGMATA refusal was reproduced. The private request and installed game runtime hashes are unchanged.

    No game, GPU, Linux or full application build was run for this diagnostic. ANYPS5_ENABLE_SPIRV_TOOLS remains OFF. This comparison does not establish a game/rendering fix; the captured payload remains private.

    AI-assisted testing and report: yes (Codex).

  9. overlordxrz-source commented on Oct 16, 2026

    @overlordxrz-source
    Contributor

    @Ondra97 @cookie42073-cmd I think I know why the cached candidate still runs out of steps on this request, and I have a patch to try on the retained request.

    #6645 (and the earlier candidate) caches a true result only if no cycle was hit while proving it. A loop header always reaches itself through its back edge, so the header's own result is never cached, and false results aren't cached at all. So every outer path into a loop proves the loop again, and with saved EXEC masks in nested loops the walk grows about 4x per level. That fits your counters: about 60 states, and the budget runs out at every size you tried.

    The patch evaluates each (value, proof) pair once:

    • It keeps false results. An open cycle only ever assumes true, so false is final.
    • It keeps true results that didn't rely on an open cycle.
    • It reuses a result that relied on an open cycle while that cycle is open, promotes it when the cycle holds, and drops it (with everything derived under it) when the cycle fails.

    The budget stays at 512. On a standalone model of the walk (the same proof rules over a mock IR), a nest of n loops takes 8n+4 steps. main takes 49, 257, 1153, 4865, ... steps for 1 to 4 levels, and #6645 takes 40, 232, 1096, 4744, .... On 400k random cyclic graphs the result matches the exact fixed point of the proof.

    Branch: overlordxrz-source@b76d8d3 (one commit on main 93e54ce).

    Could you run the same CPU-only replay of a9f1f031... on that commit, without #6180 or #6645, and post the exit code and both CPU controls? If it compiles the request, I'll open it as a PR, or fold it into #6645 if @DangerChamp prefers.

    AI-assisted: yes (Claude). I haven't run the title or a GPU.

  10. Ondra97 commented on Oct 16, 2026

    @Ondra97
    ContributorAuthor

    @overlordxrz-source Yes: your exact b76d8d31bf4cf97d53ec52f45b0dc3937fac5532 compiles the retained PRAGMATA request at the unchanged 512-step budget. Replay exits 0 and produces 553,844 SPIR-V words. Both CPU controls pass, including the helper-inclusive and asymmetric-half negatives.

    I also ran a same-base control using its parent 93e54cedd3dd49eabf1c589f020b54758e0b6461, with identical test additions:

    Production source PRAGMATA replay agc_shader_entry_lane_pixel_replay (NHL26) agc_shader_memory + saved-EXEC loop/negative cases
    Parent 93e54cedd, budget 512 Exit 1, original lane-63 refusal Fail, lane-63 refusal Fail, lane-63 refusal
    Your exact b76d8d31, budget 512 Exit 0; 553,844 SPIR-V words Pass Pass

    Both selected CTest cases passed with zero skips. After the parent control I restored the candidate, explicitly rebuilt both CPU targets, and repeated the green result (2/2, zero skips, 0.30 s).

    9536 code words, 4 user data, 2 memory regions, wave64
      recompiled: 553844 SPIR-V words
    

    Request SHA-256 remains a9f1f0318f46c97929ccebfffcfd8d9bebc8cbcfab132cabd4369bcc52f4a82e.

    Exact source and test setup

    • New isolated checkout of your exact commit, with the pinned submodule revisions verified. The only production difference from the parent is your published commit; no production code from fix(shader): budget pixel lane proof for NHL 26 replay #6180 or fix(shader): reuse completed pixel lane proofs #6645, no raised budget, no proof instrumentation, and no test(agc): align the indirect arguments block at run time #6452 build prerequisite was applied.
    • I reused only the NHL26 replay test harness/fixture from the earlier comparison and the unchanged synthetic saved-EXEC CPU loop/negative additions from the public cache-candidate package. Your commit does not register the NHL26 replay control, so these test-only additions were necessary to run the same two controls. The exact applied harness diff is below; the publicly sourced NHL26 fixture is unchanged, SHA-256 10e4baf422516704748773bd95fcbf423eb3ef7808c6e2e8f2e9242e6d3d8adb.
    • Windows, MinGW GCC 15.2, Release, BUILD_TESTING=ON, ANYPS5_NO_SHADER_CACHE=1. Both agc_shader_replay and agc_shader_memory_tests were explicitly built for each production variant.
    • After the comparison I removed all temporary harness additions, verified a clean checkout at your exact commit, rebuilt the CPU tools, and ran the private replay again: exit 0, the same 553,844 words. The unmodified native agc_shader_memory control also passed. The earlier worktree/build and the installed game runtime were not changed.

    ANYPS5_ENABLE_SPIRV_TOOLS was OFF, matching the earlier runs. This establishes successful CPU compilation of this retained request and the selected controls; I have not validated this generated SPIR-V with SPIRV-Tools or tested game startup/rendering/GPU execution. The captured payload remains private.

    Exact test-only harness diff (SHA-256 0c2baf07303c947c38884f7b2baeba2340ebdbcb7287d772c17415b9e99821a9)

    The fixture payload is omitted; it is the same public NHL26 fixture used previously. This diff contains test source and CTest registration only.

    --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt
    +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt
    @@ -1526,8 +1526,10 @@
         add_test(NAME agc_shader_layout_pair_off COMMAND agc_shader_memory_tests --layout-pair)
         set_tests_properties(agc_shader_layout_pair PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
         set_tests_properties(agc_shader_layout_pair_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_PERF_LAYOUT_PAIR=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
    -    if(WIN32)
    -        set_tests_properties(agc_shader_bindless agc_shader_srt_read_patching agc_shader_type_zero_image_materialization agc_shader_frontend_pair agc_shader_frontend_pair_off agc_shader_layout_pair agc_shader_layout_pair_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
    +    add_test(NAME agc_shader_entry_lane_pixel_replay COMMAND agc_shader_memory_tests --entry-lane-5398)
    +    set_tests_properties(agc_shader_entry_lane_pixel_replay PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
    +    if(WIN32)
    +        set_tests_properties(agc_shader_bindless agc_shader_srt_read_patching agc_shader_type_zero_image_materialization agc_shader_frontend_pair agc_shader_frontend_pair_off agc_shader_layout_pair agc_shader_layout_pair_off agc_shader_entry_lane_pixel_replay PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
         endif()
         foreach(agcTest IN ITEMS agc_vertex_input_tests agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_dpp_write_tests agc_masked_select_tests agc_ir_reclaim_tests agc_vector_window_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests agc_formatted_buffer_byte_stores_tests ${agcExecutionTests} agc_fma_mix_once_tests)
             if(agcTest STREQUAL "agc_driver_constant_store_tests" OR agcTest STREQUAL "agc_driver_flat_apertures_tests" OR agcTest STREQUAL "agc_driver_pipeline_optimized_readback_tests")
    --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
    +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
    @@ -14,6 +14,7 @@
     #include "IntermediateRepresentation/IrBuilder.hpp"
     #include "IntermediateRepresentation/IrProgram.hpp"
     #include "SpirvBackend/SpirvAnalysis.hpp"
    +#include "SavedExecMaskLoop.hpp"
     #if ANYPS5_ENABLE_SPIRV_TOOLS
     #include "SpirvBackend/SpirvOptimizer.hpp"
     #endif
    @@ -28,11 +29,13 @@
     #include <cstddef>
     #include <cstdlib>
     #include <cstring>
    +#include <fstream>
     #include <initializer_list>
     #include <iostream>
     #include <map>
     #include <future>
     #include <memory>
    +#include <sstream>
     #include <stdexcept>
     #include <span>
     #include <string>
    @@ -2311,6 +2314,18 @@
             {0u, 0x38000000u, 0x88000000u, spv::OpGroupNonUniformBitwiseOr, "half-wave reduction: the wave64 OR scan did not read lane 31 as a subgroup OR on 32 lanes"},
         }};
         for (const auto& reduction : scans) require(reductions(pixel(scan(reduction.identity, reduction.vector, reduction.scalar), 32u, true), reduction.reduce) == 1u, reduction.message);
    +    auto loopScan = scan(0xffffffffu, 0x26000000u, 0x83800000u);
    +    const auto savedMaskLoop = AgcTest::SavedExecMaskLoop(3u);
    +    loopScan.insert(loopScan.begin(), savedMaskLoop.begin(), savedMaskLoop.end());
    +    auto helperLoop = loopScan;
    +    helperLoop[0] = 0xbea204c1u;
    +    expectFailure([&] { static_cast<void>(pixel(helperLoop, 32u, true)); }, "v_readlane_b32 of lane 63 is outside the 32-lane host subgroup", "half-wave reduction: a loop initialized with helper lanes was proven to exclude them");
    +    auto upperHelperLoop = loopScan;
    +    upperHelperLoop[0] = 0xbea2037eu;
    +    upperHelperLoop.insert(upperHelperLoop.begin() + 1, 0xbea303c1u);
    +    expectFailure([&] { static_cast<void>(pixel(upperHelperLoop, 32u, true)); }, "v_readlane_b32 of lane 63 is outside the 32-lane host subgroup", "half-wave reduction: the low-word loop proof was reused for helper-inclusive upper lanes");
    +    require(reductions(pixel(loopScan, 32u, true), spv::OpGroupNonUniformUMin) == 1u, "half-wave reduction: a loop-carried saved EXEC mask exhausted the entry-lane proof on 32 lanes");
    +    require(halves(pixel(loopScan, 64u, true), spv::OpGroupNonUniformUMin, 0xffffffffu), "half-wave reduction: a loop-carried saved EXEC mask did not preserve the two half-wave UMin reductions");
         auto comparedScan = scan(0u, 0x38000000u, 0x88000000u);
         *std::find(comparedScan.begin(), comparedScan.end(), 0xbe98047eu) = 0x8a986a7eu;
         comparedScan.insert(comparedScan.begin(), 0x7c080080u);
    @@ -2426,6 +2441,18 @@
         compared.insert(compared.begin() + 3, {0xd4c50006u, 0x00021680u});
         expectFailure([&] { static_cast<void>(pixel(compared, 32u, true)); }, lane63, "half-wave reduction: a scan selected by a compare under the all-lanes EXEC read lane 63");
         expectFailure([&] { static_cast<void>(pixel(umin, 8u, true)); }, "v_permlanex16_b32 from lanes 16-31 is outside the 8-lane host subgroup", "half-wave reduction: the scan built on an 8-lane subgroup");
    +}
    +
    +void verifyNhl26Wave64PixelLane63Replay() {
    +    using namespace ShaderRecompiler;
    +    std::ifstream file("core/libs/prx/libSceAgcDriver/tests/data/nhl26-wave64-pixel-lane63.req", std::ios::binary);
    +    require(file.good(), "NHL 26 wave64 pixel request fixture could not be opened");
    +    std::ostringstream serialized;
    +    serialized << file.rdbuf();
    +    const auto request = RequestSerializer{}.Deserialize(serialized.str());
    +    require(request.request.shader.stage == ShaderStage::Fragment && request.request.context.waveSize == 64u && request.request.target.subgroupSize == 32u, "NHL 26 lane-63 fixture no longer describes a wave64 pixel shader on a 32-lane host");
    +    const auto handle = PrepareShader(request.request);
    +    require(!GetPreparedArtifact(*handle).spirv.Words().empty(), "NHL 26 wave64 pixel shader did not produce SPIR-V");
     }
     
     void verifyNarrowVertexWave() {
    @@ -5156,6 +5183,11 @@
             if (argc == 2 && std::string_view(argv[1]) == "--layout-pair") {
                 verifyLayoutPair();
                 std::cout << "Both push-constant layouts of a pixel shader compile from one program unchanged\n";
    +            return 0;
    +        }
    +        if (argc == 2 && std::string_view(argv[1]) == "--entry-lane-5398") {
    +            verifyNhl26Wave64PixelLane63Replay();
    +            std::cout << "The NHL 26 wave64 pixel lane-63 shader compiles for a 32-lane host\n";
                 return 0;
             }
             if (argc == 2 && std::string_view(argv[1]) == "--bindless") {
    --- /dev/null
    +++ b/core/libs/prx/libSceAgcDriver/tests/SavedExecMaskLoop.hpp
    @@ -0,0 +1,19 @@
    +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_SAVEDEXECMASKLOOP_HPP
    +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_SAVEDEXECMASKLOOP_HPP
    +
    +#include <cstdint>
    +#include <vector>
    +
    +namespace AgcTest {
    +
    +inline std::vector<std::uint32_t> SavedExecMaskLoop(std::uint32_t rounds) {
    +    std::vector<std::uint32_t> code{0xbea2047eu, 0xbea40380u};
    +    for (std::uint32_t step = 0; step < rounds; ++step) code.insert(code.end(), {0xbefe0422u, 0x7c080080u, 0x8aa26a7eu});
    +    const auto branch = 0xbf850000u | static_cast<std::uint16_t>(2 - static_cast<int>(code.size() + 3));
    +    code.insert(code.end(), {0x80248124u, 0xbf0a8424u, branch, 0xbefe0422u});
    +    return code;
    +}
    +
    +}
    +
    +#endif

    AI-assisted testing and report: yes (Codex).

  11. oneandonlydean commented on Oct 17, 2026

    @oneandonlydean
    Collaborator

    @overlordxrz-source thanks for b76d8d3. It now has two independent confirmations at the unchanged 512-step budget: Ondra97's PRAGMATA replay above, and cookie42073-cmd's NHL 26 rerun on main 6a32550 in #6180.

    We're deciding between raising the budget (#6180), your proof cache, or both. Since b76d8d3 sits directly on main rather than on #6645, could you open it as its own PR? Or, if you and @DangerChamp prefer, it could replace the cache in #6645. Either works for us; we'd just like one PR to review and test.

  12. Ondra97 commented on Oct 17, 2026

    @Ondra97
    ContributorAuthor

    @cookie42073-cmd Update after refreshing main and repeating the live test (2026-10-17): the retained shader request passes on main plus the b76 proof-cache helper, but I still cannot confirm a working PRAGMATA menu or scene. The new run ended because of host memory pressure, not a spontaneous crash.

    Refreshed main comparison

    The pinned main is ca82d7d882b0ac17d955f6e107fe2eb0cae244af, which already includes #6180 and its 2048-state budget. I compared it with the same main plus EntryLaneWalk and its <map> include from b76d8d31bf4cf97d53ec52f45b0dc3937fac5532. I retained main's budget of 2048 and all code outside that helper, including the current state.spannedWave reduction guard. This is a new integration test; it is separate from the exact-b76/512 result preserved below.

    Both builds used SPIRV-Tools ON and the same retained request and public CPU test additions:

    Configuration Retained request Two CPU controls
    Unmodified main ca82d7d, budget 2048 Exit 1, original lane-63 refusal 1/2 pass; saved-EXEC additions in agc_shader_memory fail
    Same main + b76 cache helper, budget 2048 Exit 0; 534,483 SPIR-V words, validation passes before and after default optimization 2/2 pass, zero skips

    The request SHA-256 remains a9f1f0318f46c97929ccebfffcfd8d9bebc8cbcfab132cabd4369bcc52f4a82e. The new optimized output SHA-256 is 3ab7d4f43b8ee11e3e4e5b0df3526a0e985b801c11e27f4225167825f02ab43e. These results show that the merged budget increase alone still does not handle this retained request. The request and generated SPIR-V remain private.

    The matching full Windows Release build passed. The title and its bundled guest libc were freshly converted, import/provider audit passed, and all 6/6 preflight tests passed, zero skips: patched_libraries, agc_shader_entry_lane_proof_budget, agc_shader_memory, native agc_driver_pixel_wave_reduction, agc_spirv_optimizer, and guest_convert_keycode. I verified all 131 installed runtime files and the converted executable/module against the prepared outputs before launching.

    Live test of main + cache

    SPIRV-Tools ON, default optimization, shader cache disabled, APS5_SPANNED_WAVE unset; same host and title package as the earlier runs below.

    • Duration: 3 min 32 s. The user confirmed a black/empty game window during the run; no menu or scene was confirmed. Computer Use remained blocked after an accidental Escape, so I do not have a reliable automated game screenshot for this attempt.
    • Private committed memory reached 14.0 GiB. Sampled free physical RAM fell to 368 MiB; a monitor terminated my verified game process after three successive samples below 768 MiB. The recorded exit -1 is from that intervention; the launcher's timeout flag is false. This is not a spontaneous-crash result or a completed startup benchmark.
    • The completed stdout/stderr contain 51 image-table snapshot/prebuild fallback messages and 1 WAIT_REG_MEM_64 timeout. They contain no original lane-63 refusal, SPIR-V emission failure, or SPIR-V validation failure. This does not establish that every previously failing shader was reached live.
    • No new BMP capture was produced with capture every 120 presents; that sampling result alone does not establish zero presents.

    The GPU wait diagnostic is:

    queue 0x20 WAIT_REG_MEM_64 at 0x210c8000e0 timed out after 1000ms
    (function 3 ref 0x63 mask 0xffffffffffffffff value 0x1)
    recorder: table miss (refusal 0), open batch writes it 0,
    snapshot write overlaps 0, pending completion labels 0,
    write-back completions 0, pending label age -1.0 ms,
    write generation 0 seen 0, queue 0 dormant 0,
    completions pending 0, label due 0
    

    The preceding packets were SET_SH_REG, SET_UCONFIG_REG, then this wait. I have not established whether the table fallback, GPU wait, or memory growth causes the empty image. I am reporting these as observations for follow-up, not claiming a title unblock or attributing the allocations to a specific subsystem.

    The test process and launcher have exited. All six shared archives were protected by verified read-only sharing leases for the process lifetime; original eboot/libc hashes are unchanged. The main-plus-cache runtime remains installed and its source diff/test additions are retained locally. No new commit or PR was created.

    Earlier exact-b76/512 validation and live results, before refreshing main

    @cookie42073-cmd I completed the requested SPIRV-Tools ON check. The retained PRAGMATA request passes validation before and after optimization on exact b76d8d31bf4cf97d53ec52f45b0dc3937fac5532, with the budget still 512. Replay exits 0 and returns 535,172 SPIR-V words.

    CPU validation Result
    SPIRV-Tools validation before optimization Pass
    Default optimization and validation afterward Pass
    Retained request replay Exit 0; 535,172 words
    agc_shader_entry_lane_pixel_replay and agc_shader_memory, with the same saved-EXEC/helper/asymmetric-half tests 2/2 pass, zero skips (1.95 s)

    Request SHA-256 is unchanged: a9f1f0318f46c97929ccebfffcfd8d9bebc8cbcfab132cabd4369bcc52f4a82e. The optimized output SHA-256 is 784f30e0dbc068057ddc22a97106e9a9dd39b28ddd5a073039d87e51acc1ea9d. The payload and generated module remain private.

    I used the original b76 base 93e54cedd3dd49eabf1c589f020b54758e0b6461, not the newer main integration. EntryLaneWalk is unchanged from b76 (production Git blob 9f59064e2e08a50a71fe77f10c9a08309cf9d4e9); no #6180 budget change, #6645 production code or proof instrumentation was added. The CPU test additions are identical to my previous comparison. I applied only the f1905053 replay-tool API fix needed to compile Disassemble(data(), size(), ...) with SPIRV-Tools ON. Both CPU targets were explicitly rebuilt in a separate Release build, shader cache disabled, APS5_SPIRV_OPT unset for the validation above.

    Separate live-game attempt

    At the user's request I also built and installed a matching full Windows runtime and visibly launched the title twice. The full default build passed with the same replay-tool fix and the merged #6452 test-alignment prerequisite. Import audit found zero absent imports/provider mismatches and no missing libraries. patched_libraries, guest_convert_keycode and the native agc_driver_pixel_wave_reduction test passed 3/3, zero skips.

    Host: Ryzen 9 7900X, 32 GB RAM, RTX 4070 Ti SUPER, Windows 11 Pro 26300, NVIDIA Windows driver 32.0.16.1742, MinGW GCC 15.2. Same candidate/runtime, input package and disabled shader cache in both live attempts:

    Live configuration Observed result
    SPIRV-Tools ON, default optimization Black window at visual checkpoints; no menu observed; stopped after 6 min 19 s with high CPU/memory use
    SPIRV-Tools ON, APS5_SPIRV_OPT=none Empty dark-gray window and an FPS title counter at the final visual checkpoint; no menu/scene observed; stopped after 5 min 01 s

    I stopped both manually after the visual checks and memory pressure; normal window close did not finish, so I terminated my test processes. The recorded -1 exit codes come from that intervention and are not spontaneous-crash results. Private committed memory at termination was approximately 15.1 GiB and 22.3 GiB respectively. Free physical memory fell to about 675 MiB during the first run.

    Both logs repeatedly report shader library prebuild skipped: image table snapshot disagrees with the static tables. They also log WAIT_REG_MEM_64 timeouts on queue 0x20 (reference 0x63, observed value 0x1): 1 warning with default optimization and 11 with optimization disabled. These are observations for follow-up; I have not established the cause of the empty image, and the FPS counter does not demonstrate correct rendering. Neither live run logged the original lane-63 refusal, but I am not claiming a title unblock or that the live runs reached every previously failing shader.

    The previous runtime was backed up, all six shared game archives were protected by verified read-only sharing leases, and original title inputs remain unchanged. Temporary test/tool source additions are restored; the candidate checkout is clean. The new runtime remains installed for follow-up.

    AI-assisted testing and report: yes (Codex).

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