From 5a8fe21ad555d5f335d3f7b4c6eda69a63279c7a Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 6 Jun 2026 19:52:16 +0200 Subject: [PATCH] =?UTF-8?q?Iterate-2.BF.=CE=B3:=20refine=20is=5Fin=5Fcallb?= =?UTF-8?q?ack=20gate=20to=20per-thread=20exclusion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lockstep vsync delivery was capped at 54/run despite the ticker firing 333 periods and dispatcher being called 1.2M times. Root cause: the blanket `is_in_callback()` gate skipped dispatch entirely whenever the async audio path held `interrupts.saved`, which is essentially the entire boot (audio worker rarely hits its LR_HALT_SENTINEL between back-to-back callbacks). 5.85M dispatch_skip_in_callback events drowned out the 55 with-pending windows. Graphics dispatch (iterate-2.BE) runs the ISR synchronously and restores the borrowed context before returning — it doesn't touch `interrupts.saved`. The only real conflict is if graphics picks the *same* thread audio borrowed (which would stomp audio's SavedCallbackCtx). Replace the blanket gate with per-thread exclusion: when audio is mid-flight, exclude only its `injected_ref` from victim selection. Falls through to the existing no-victim drop if that's the only candidate. Lockstep (50M instr): gpu.interrupt.delivered{source=0} 54 → 295 (5.5×), all 333 ticker periods either delivered or unarmed (no more queue_full_drops). Wallclock unchanged ~3 s. Parallel (30M instr): 1193 → 3458 baseline lift (2.9×), no regression. Tests: xenia-kernel 127/127, xenia-app 5/5 non-ignored. Lockstep goldens will drift (interrupts.delivered is in the digest); deferred to next iterate. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/xenia-app/src/main.rs | 38 +++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/crates/xenia-app/src/main.rs b/crates/xenia-app/src/main.rs index 004dad6..0f1aaec 100644 --- a/crates/xenia-app/src/main.rs +++ b/crates/xenia-app/src/main.rs @@ -3262,23 +3262,39 @@ fn dispatch_graphics_interrupts( return; }; - // Audio injection (audit-048 Plan B) still uses the asynchronous - // LR-sentinel path. If an audio callback is mid-flight we must not - // try to clobber the borrowed context — bail until the audio path - // returns through the worker_prologue restore. - if kernel.interrupts.is_in_callback() { - return; - } + // Iterate-2.BF.γ: graphics dispatch is fully synchronous (host-driven, + // iterate-2.BE) — it borrows a guest thread, runs the ISR to + // LR_HALT_SENTINEL, and restores all in-call before returning. So it + // CAN safely coexist with an audio callback mid-flight, *as long as we + // pick a different victim thread* than the one audio borrowed. The old + // blanket `is_in_callback()` gate caused 5.85M skipped dispatches in + // lockstep boot (vs 55 with-pending dispatches) — audio is essentially + // always mid-flight on its dedicated worker, which choked vsync + // delivery at ~54. Exclude only audio's borrowed thread; the queue + // drains synchronously and graphics ISR completion does not touch + // `interrupts.saved` (used exclusively by the async audio path). + let audio_borrowed = if kernel.interrupts.is_in_callback() { + kernel.interrupts.injected_ref + } else { + None + }; while let Some(source) = kernel.interrupts.peek_next() { // Victim selection: Ready first, then Blocked (canary's // `XThread::GetCurrentThread()` analog — any live thread will // do for borrowing context). Skip Idle/Exited/ServicingIrq. + // Skip the audio-borrowed thread (if any) to avoid clobbering + // its `SavedCallbackCtx` mid-flight. + let excluded = audio_borrowed; let mut victim: Option = None; 'outer_ready: for (hw_id, slot) in kernel.scheduler.slots.iter().enumerate() { for (idx, t) in slot.runqueue.iter().enumerate() { + let r = xenia_cpu::ThreadRef::new(hw_id as u8, idx as u16); + if excluded == Some(r) { + continue; + } if matches!(t.state, HwState::Ready) { - victim = Some(xenia_cpu::ThreadRef::new(hw_id as u8, idx as u16)); + victim = Some(r); break 'outer_ready; } } @@ -3286,8 +3302,12 @@ fn dispatch_graphics_interrupts( if victim.is_none() { 'outer_blocked: for (hw_id, slot) in kernel.scheduler.slots.iter().enumerate() { for (idx, t) in slot.runqueue.iter().enumerate() { + let r = xenia_cpu::ThreadRef::new(hw_id as u8, idx as u16); + if excluded == Some(r) { + continue; + } if matches!(t.state, HwState::Blocked(_)) { - victim = Some(xenia_cpu::ThreadRef::new(hw_id as u8, idx as u16)); + victim = Some(r); break 'outer_blocked; } }