WEDGE-PROGRESSION: deadline-fire + nt_create_event polarity
Two surgical fixes from 2026-05-28 that take VdSwap from 1 (stuck since
AUDIT-049 on 10 May) to 6 — first real gameplay-direction rendering
progression. Boot now reaches host_ns=208s (was 51s post-2.V, 767ms
pre-2.V).
2.AF (xenia-app/main.rs:2475, +18 LOC):
Per-round drain of Scheduler::timed_waits in coord_pre_round, right
after fire_due_timers. advance_to_next_wake_if_due was only called
from coord_idle_advance (zero-threads-runnable path), so under load
expired wait-deadlines never fired. tid=5's 42.95ms deadline sat
unfired 29s+ in 2.V trace. Post-fix: tid=5 Blocked->Ready, events
13M->45M (3.5x), boot reaches 152s.
2.AI (xenia-kernel/exports.rs:3043, +16 LOC):
Fix manual_reset polarity inversion in nt_create_event:
-let manual_reset = ctx.gpr[5] != 0;
+let manual_reset = ctx.gpr[5] == 0;
Canary reference: xenia-canary xboxkrnl_threading.cc:620
ev->Initialize(!event_type, !!initial_state)
manual_reset = !event_type, so event_type=0 (NotificationEvent) ->
manual, event_type=1 (SynchronizationEvent) -> auto. Our Ke-path
(ensure_dispatcher_object) was already correct; Nt-path was inverted.
Sylpheed's frame-sync Event (event_type=1 + initial_state=1) was
mis-classified manual-reset+signaled, staying signaled forever ->
tid=1 main loop spun at 23kHz (2,798x canary's 60Hz). Post-fix:
tid=1 NtWait calls 3,233,583->51 (63,400x drop), VdSwap 2->6 (3x
rendering progression), boot reaches 208s.
Tests: 300 xenia-cpu / 227 xenia-kernel / 5 xenia-app / 19 xenia-path
+ ~30 smaller - all PASS, 0 regressions. Determinism preserved:
2x cold runs bit-identical at 65,691,821 events.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2473,6 +2473,24 @@ fn coord_pre_round(
|
|||||||
}
|
}
|
||||||
|
|
||||||
kernel.fire_due_timers();
|
kernel.fire_due_timers();
|
||||||
|
// 2.AF — fire expired wait-deadlines under load. Without this call
|
||||||
|
// `advance_to_next_wake_if_due` only runs in `coord_idle_advance`
|
||||||
|
// (no Ready threads), so a thread whose `KeWait*` deadline expires
|
||||||
|
// while other threads keep the scheduler busy sits Blocked forever
|
||||||
|
// (2.AD: tid=5's 42.95ms deadline expired by ~22s but never fired
|
||||||
|
// for 29s+). Drain every entry whose deadline `<=` the current
|
||||||
|
// guest timebase (the same `now` `fire_due_timers` uses, so the
|
||||||
|
// two stay in lock-step) and let `handle_timeout_wake` stamp
|
||||||
|
// `STATUS_TIMEOUT` and scrub the waiter from each handle.
|
||||||
|
// Deterministic: `Scheduler::ctx(0).timebase` is the guest-cycle
|
||||||
|
// timebase, not host_ns.
|
||||||
|
loop {
|
||||||
|
let now = kernel.scheduler.ctx(0).timebase;
|
||||||
|
let Some((r, reason)) = kernel.scheduler.advance_to_next_wake_if_due(now) else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
kernel.handle_timeout_wake(r, reason);
|
||||||
|
}
|
||||||
try_inject_graphics_interrupt(kernel);
|
try_inject_graphics_interrupt(kernel);
|
||||||
if kernel.xaudio_tick_enabled {
|
if kernel.xaudio_tick_enabled {
|
||||||
try_inject_audio_callback(kernel);
|
try_inject_audio_callback(kernel);
|
||||||
|
|||||||
@@ -3038,9 +3038,23 @@ pub(crate) fn close_handle_internal(state: &mut KernelState, handle: u32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn nt_create_event(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
|
fn nt_create_event(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
|
||||||
// r3 = handle_ptr, r4 = obj_attrs, r5 = event_type, r6 = initial_state
|
// r3 = handle_ptr, r4 = obj_attrs, r5 = event_type, r6 = initial_state.
|
||||||
|
//
|
||||||
|
// 2.AI — Xenon DISPATCHER_HEADER `Type` (NT convention):
|
||||||
|
// 0 = NotificationEvent (manual-reset)
|
||||||
|
// 1 = SynchronizationEvent (auto-reset)
|
||||||
|
// Canary mirrors this at `xboxkrnl_threading.cc:620`
|
||||||
|
// (`ev->Initialize(!event_type, !!initial_state)`) and our own
|
||||||
|
// `ensure_dispatcher_object` (above, type=0→manual, type=1→auto).
|
||||||
|
//
|
||||||
|
// The prior polarity here was inverted (`event_type != 0` → manual),
|
||||||
|
// which silently mis-classified Sylpheed's per-frame VSync gate as
|
||||||
|
// manual-reset+initial-signaled. `handle_consume` is a no-op for
|
||||||
|
// manual-reset events, so the wait fast-path returned SUCCESS every
|
||||||
|
// call (1.05 M iterations on the wedge handle 0x10e8 in 2.AF, zero
|
||||||
|
// signal.match) instead of blocking ~17 ms for the next VSync.
|
||||||
let handle_ptr = ctx.gpr[3] as u32;
|
let handle_ptr = ctx.gpr[3] as u32;
|
||||||
let manual_reset = ctx.gpr[5] != 0;
|
let manual_reset = ctx.gpr[5] == 0;
|
||||||
let signaled = ctx.gpr[6] != 0;
|
let signaled = ctx.gpr[6] != 0;
|
||||||
let handle = state.alloc_handle_for(KernelObject::Event {
|
let handle = state.alloc_handle_for(KernelObject::Event {
|
||||||
manual_reset,
|
manual_reset,
|
||||||
|
|||||||
Reference in New Issue
Block a user