Priority aging in xenia-cpu/scheduler.rs:pick_runnable
(effective_priority = base + age_bonus(now_round - last_run_round),
capped at +31, AGING_ROUNDS_PER_BONUS=1). Strict-priority was parking
priority=0 threads behind CPU-bound priority=15 audio mixer
(sub_824D1328 guest spinwait at PC=0x824d1404 on CPU5). Aging
eventually picks the starved thread, breaking the producer-consumer
cycle that caused 5-tid wedge at PC=0x824ac578 since AUDIT-049 (10 May).
Cascade observed: tid=13 clean exit; events 121K -> 13M (107x); last
host_ns 767ms -> 51,011ms (66x); 8 new threads spawn; VdSwap 1 -> 2.
Complete two-day iterate sequence (2026-05-27 -> 2026-05-28):
- 2.F: VdSwap drain timeout 900ms -> 1ms (xenia-gpu/handle.rs); 876x
perf win on VdSwap kernel callback
- 2.H: vA0000000 physical heap bucket added (state.rs, exports.rs);
ctx_ptrs now in 0xA0000000-0xBFFFFFFF range matching canary
- 2.L: Phase-A diff harness categorized [return_value mismatch],
[status mismatch], [args_resolved.path mismatch] tags
(tools/diff-events/diff_events.py); closes reading-error #41
(silent test-harness state leak invalidating trace diffs)
- 2.M: always-on exit-thread-state.json sibling to Phase-A JSONL
(event_log.rs + xenia-app/main.rs); closes reading-error #42
(Phase-A blind to blocked-forever waits)
- 2.Q: signal.match kernel instrumentation in NtSetEvent /
NtReleaseSemaphore / KeSetEvent / KeReleaseSemaphore
(exports.rs); emits target_handle + waiter_count + waiter_tids
- 2.T: wake.requested kernel instrumentation in wake_eligible_waiters
(exports.rs); emits target_tid + transition + new_state
- 2.V: scheduler priority aging (xenia-cpu/scheduler.rs) [keystone]
Plus accumulated WIP from earlier May (contention_manifest,
phase_b_snapshot, xam/xaudio enhancements, analysis db, xex loader,
xenia-app main loop, etc.). Audit-runs/ artifacts remain untracked
per project convention.
Tests: 300 xenia-cpu / 227 xenia-kernel / 5 xenia-app / 19 xenia-path
/ 30+ smaller suites -- all PASS, 0 regressions. Determinism preserved
(2x cold runs bit-identical at 13,003,881 events post-2.V).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
8.2 KiB
address, classification, confidence, last_audit, aliases
| address | classification | confidence | last_audit | aliases | ||
|---|---|---|---|---|---|---|
| 0x821C4EB0 | vtable_method | high | 061 |
|
sub_821C4EB0 — silph::GamePart_Title::UImpl member fn (AUDIT-061: NOT a branch-divergence gate)
Synopsis
Member function on class silph::GamePart_Title::UImpl (vtable 0x820a3e00). AUDIT-061 falsified the "conditional-branch divergence in [+0x44, +0xE0]" framing: all 4 branches in that range are decided bit-identically in canary and ours. The actual divergence is the call bl 0x821CC3F8 at PC 0x821C4F14: in canary the call returns to 0x821C4F18 and the rest of sub_821C4EB0 executes through the 5 bl 0x821CEDF8 sites at +0x198..+0x240; in ours the call enters the chain sub_821CC3F8 → sub_821CBA08 → sub_821CB030 and never returns (tid=13 wedge inside sub_821CB030 = AUDIT-049 wedge handle, NtCreateEvent at +0x128 → INFINITE wait). AUDIT-056's "5× canary / 0× ours" callsite count is an indirect consequence of the upstream wedge, not a branch-decision asymmetry in this fn.
Evidence
- AUDIT-049: appears in tid=13 thread-create chain —
sub_821748F0 → sub_821C4EB0 (UImpl@GamePart_Title@silph) → sub_821CC3F8 → sub_821CBA08 → sub_821CB030. - AUDIT-056: caller-LR
0x821C4F2C / 0x821C5014 / 0x821C5048are post-blPCs inside this fn. Reportedsub_821CEDF85× canary / 0× ours. - AUDIT-059: in the wedge's wait-thread frame-4 saved-r29 the vtable is
0x820a3e00 = .?AUImpl@GamePart_Title@silph@@, confirming class membership. - AUDIT-061 (READ-ONLY canary multi-PC probe @ ~2:00 wallclock; ours
--branch-probe@ -n 500M):- Both engines call sub_821C4EB0 exactly 1× at this horizon. Same caller LR=0x82174A80 (canary tid=17, ours tid=13).
- Canary probe fires 17× covering entry + post-bl block entries + all 4 cond-branches: B1
beq cr6 NOT taken(cr6=.G., r3=0xBC220008≠0), B2bne cr6 NOT taken(cr6=..E, lbz @ 0x828F3284 = 0), B3beq cr6 TAKEN(cr6=..E, lwz r3,92(r30) == 0), B4bgt cr6 TAKEN(cr6=.G., [r27+4] > 4). Reaches 0x821C5048 (1stbl 0x821CEDF8) and 0x821C504C (returned). - Ours probe fires 4× covering entry + 3 post-bl: 0x821C4EB0 → 0x821C4EB8 → 0x821C4ED0 → 0x821C4EEC (r3=0x40105004 returned from
bl 0x82150EF8; cr6=.G., same direction as canary). After 0x821C4EEC: never reaches 0x821C4F18 or anywhere later in the function. - Chain probe (separate run) confirms ours's tid=13 enters sub_821CC3F8 (cycle 2069) → sub_821CC3F8+0x38 post-alloc (2249) → sub_821CBA08 (2258) → sub_821CB030 (3242), then stalls. Canary's tid=17 returns out of all four and reaches 0x821CC454 (post-bl-sub_821CBA08) and 0x821C4F18 (post-bl-sub_821CC3F8) cleanly.
- First divergent INSTRUCTION (not branch):
bl 0x821CC3F8at PC0x821C4F14. First divergent state: ours's r3 at function entry to sub_821CC3F8 is0x40105004(40xxxxxx host-allocator region) vs canary's0xBC220008(BCxxxxxx region) — but this VA difference is the AUDIT-043 ε-class (allocator region drift) and is BENIGN here; sub_821CC3F8 dereferences r3 as a pool handle the same way in both engines and downstream allocation succeeds (sub_82150EF8 returns valid pointer in both).
Activation
Vtable method. Reached via bctrl from class-owning code in the boot UI / GamePart_Title state machine. Indirect; the dispatch site PC and vtable slot index need DB cross-reference (see Open questions).
Static graph
- Caller chain at the wedge site (AUDIT-049):
sub_821C4EB0 ← sub_821748F0(top-level)- flows down to
sub_821CC3F8 (GamePart_Title)→sub_821CBA08→sub_821CB030(where wedge fires)
- Callees in source order:
0x821C4EB4 bl 0x825F0F7C— save-GPRs prologue helper0x821C4ECC bl 0x8284DA7C— XAM importXNotifyPositionUI(xam.xex ord 652); r3=0xA → returns 0 in both engines.0x821C4EE8 bl 0x82150EF8— pool allocator (called with allocator table @[0x828E0000+11028], size=4); returns pointer in both engines (canary BC220008, ours 0x40105004).0x821C4F14 bl 0x821CC3F8— first divergent instruction (AUDIT-061): returns in canary, wedges in ours.0x821C4F2C bl 0x82187C30— only reached in canary at this horizon.0x821C4F60 bl 0x82172370— only reached in canary.0x821C4F74 bl 0x824AA3E0— conditional on prior beq; canary takes the SKIP-bl path (B3 = taken).0x821C5048 / 0x821C5074 / 0x821C50A0 / 0x821C50C8 / 0x821C50F0 bl 0x821CEDF8— 5 sites in the bgt-taken path; only reached in canary.
- Conditional branches in
[+0x44, +0xE0](enumerated AUDIT-061):- B1
0x821C4EF8 beq cr6, 0x821C4F20— aftercmplwi cr6, r3, 0(r3 = sub_82150EF8 return). Decided NOT taken in both. - B2
0x821C4F3C bne cr6, 0x821C4F7C— afterlbz r10, 12932(0x828F0000)+cmplwi r10, 0. Decided NOT taken in canary; UNREACHED in ours. - B3
0x821C4F70 beq cr6, 0x821C4F78— afterlwz r3, 92(r30). Decided TAKEN in canary; UNREACHED in ours. - B4
0x821C4F90 bgt cr6, 0x821C5000— aftercmplwi cr6, r11, 3, r11 =[r27+4]−1. Decided TAKEN in canary; UNREACHED in ours.
- B1
Audit log
- AUDIT-061 (2026-05-12) — Multi-PC branch probe in both engines (new canary cvar
audit_61_branch_probe_pcs, ours--branch-probe). All 4 conditional branches in[+0x44, +0xE0]decided bit-identically (B1 NOT-taken in both; B2/B3/B4 UNREACHED in ours because the function stalls earlier). First divergent BEHAVIOR is the callbl 0x821CC3F8at PC0x821C4F14— returns in canary, wedges in ours. The wedge is INSIDEsub_821CB030(chainsub_821C4EB0 → sub_821CC3F8 → sub_821CBA08 → sub_821CB030); tid=13 reaches sub_821CB030 at cycle 3242 and blocks indefinitely. Confirms AUDIT-049 wedge premise; matches AUDIT-059 γ-class missing-signaler. AUDIT-056's "5× sub_821CEDF8 canary / 0× ours" is an indirect consequence (those 5 sites are at +0x198..+0x240, downstream of the wedge). [confirmed — sub_821C4EB0 is NOT a branch-divergence gate] - AUDIT-060 (2026-05-12) — convergence confirmed this fn as the AUDIT-061 target after AUDIT-058/059's "missing activator" framing was refuted. [superseded by 061 — actual divergence is non-returning call, not a branch]
- AUDIT-056 (2026-05-10) — identified as the primary divergence-introducer. Caller-LR is IDENTICAL canary/ours but body chooses a different path. [partially falsified by 061 — the "different path" framing was true at a high level, but it's because of a non-returning call, not a divergent conditional-branch decision in
[+0x44, +0xE0]. The 5 sub_821CEDF8 callsites are downstream of the wedge.] - AUDIT-049 (2026-05-10) — placed on the tid=13 chain that ultimately creates wedge handle. [confirmed — AUDIT-061 directly observed tid=13 entering sub_821CB030 in ours]
Open questions
Enumerate every conditional branch PC in. DONE in AUDIT-061: B1/B2/B3/B4 enumerated; none divergent in decision.[0x821C4EF4, 0x821C4F90]For each branch: capture cr0/cr6/cr-of-interest.... DONE in AUDIT-061.What input register controls the first divergent branch?Moot — no branch diverges in this fn.- NEW (AUDIT-062 target): Where INSIDE sub_821CB030 does ours's tid=13 stall? AUDIT-049 hypothesized the wait at the event handle created at +0x128. Probe sub_821CB030's basic-block entries to find the highest-PC reached by tid=13 before stall; cross-reference with the NtCreateEvent / KeWaitForSingleObject sites.
- Which vtable slot is
sub_821C4EB0at in vtable0x820a3e00? (still open; cross-refxrefstable fortarget = 0x821C4EB0withkind = 'read'or'ref'in.data/.rdata).
Cross-references
- Vtable:
0x820a3e00 = .?AUImpl@GamePart_Title@silph@@(class) - Sibling class vtable:
0x820a3dc8 = .?AVGamePart_Title@silph@@(parent? aggregate?) - Callees:
sub_821CC3F8(first-divergent-call AUDIT-061),sub_821CEDF8(5× sites at +0x198..+0x240, only reached in canary) - Callers:
sub_821748F0(top of tid=13 chain; lr=0x82174A80 seen in both engines AUDIT-061) - Wedge chain: sub_821CB030 is where ours's tid=13 stalls per AUDIT-061's chain probe.
- Audits: 049, 056, 057, 058, 059, 060, 061
- Artifacts:
audit-runs/audit-056-producer-trace/,audit-runs/audit-059-gamma-wedge/,audit-runs/audit-061-sub821C4EB0-branch-diff/