Iterate-2.BF.γ: refine is_in_callback gate to per-thread exclusion

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) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-06 19:52:16 +02:00
parent 51489e34db
commit 5a8fe21ad5

View File

@@ -3262,23 +3262,39 @@ fn dispatch_graphics_interrupts(
return; return;
}; };
// Audio injection (audit-048 Plan B) still uses the asynchronous // Iterate-2.BF.γ: graphics dispatch is fully synchronous (host-driven,
// LR-sentinel path. If an audio callback is mid-flight we must not // iterate-2.BE) — it borrows a guest thread, runs the ISR to
// try to clobber the borrowed context — bail until the audio path // LR_HALT_SENTINEL, and restores all in-call before returning. So it
// returns through the worker_prologue restore. // CAN safely coexist with an audio callback mid-flight, *as long as we
if kernel.interrupts.is_in_callback() { // pick a different victim thread* than the one audio borrowed. The old
return; // 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() { while let Some(source) = kernel.interrupts.peek_next() {
// Victim selection: Ready first, then Blocked (canary's // Victim selection: Ready first, then Blocked (canary's
// `XThread::GetCurrentThread()` analog — any live thread will // `XThread::GetCurrentThread()` analog — any live thread will
// do for borrowing context). Skip Idle/Exited/ServicingIrq. // 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<xenia_cpu::ThreadRef> = None; let mut victim: Option<xenia_cpu::ThreadRef> = None;
'outer_ready: for (hw_id, slot) in kernel.scheduler.slots.iter().enumerate() { 'outer_ready: for (hw_id, slot) in kernel.scheduler.slots.iter().enumerate() {
for (idx, t) in slot.runqueue.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) { 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; break 'outer_ready;
} }
} }
@@ -3286,8 +3302,12 @@ fn dispatch_graphics_interrupts(
if victim.is_none() { if victim.is_none() {
'outer_blocked: for (hw_id, slot) in kernel.scheduler.slots.iter().enumerate() { 'outer_blocked: for (hw_id, slot) in kernel.scheduler.slots.iter().enumerate() {
for (idx, t) in slot.runqueue.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(_)) { 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; break 'outer_blocked;
} }
} }