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:
@@ -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<xenia_cpu::ThreadRef> = 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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user