[iterate-4A] diagnostics: XENIA_PROFILE wall-time profiler + probe/tooling snapshot
Handoff snapshot of the env-gated diagnostic scaffolding used across the intro-video RE. Kept out of the milestone commits (645feb8..5573ac1) to keep those clean; committed here so nothing is lost on handoff. New — XENIA_PROFILE wall-time profiler (crates/xenia-gpu/src/prof.rs): Coarse buckets attributing playback wall time to interpreter (step_block), kernel HLE (call_export), block decode/cache (lookup_or_build), texture decode, host draw, and present; prints periodic snapshots (every 500M guest instr, or every 500 presents) + a clean-exit report. Hot path is gated on a cached is_on() (one relaxed load) so it is zero-cost when XENIA_PROFILE is unset. Call sites: main.rs run_superblock / parallel worker (step_block, lookup_or_build, call_export), texture_cache ensure_cached, render.rs present + dispatch_xenos_draws. First profile (movie playback, headless single-thread lockstep): effective ~35 MIPS; interpreter body ~40% @ ~95-102 MIPS; texture decode 0.3% (cache works); present ~0%; the rest is per-block dispatch + scheduler plumbing (~13 instr/block over 229M blocks). Overhead-bound, not interpreter-body bound; the levers are coarser execution units (superblock chaining) and ultimately a JIT. Pre-existing read-only probe knobs (were uncommitted; env-gated, observe-only): XENIA_RET_CAPTURE_PC/_REG/_MEM, LOG_RESUMES, LOG_WAITS, LOG_SIGNAL, FORCE_TID, STARVE_LIMIT, INCUMBENT_PICK, INSTR_PER_MS, DUMP_FRAME, DUMP_WGSL, BIND_LOG, CONST_LOG, DISPATCH_REC, AUDIT_PC_TRACE. Tooling: sylph-run.sh (movie oracle loop, 180s default timeout), zq.py (DuckDB disasm/xref helper). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1351,10 +1351,16 @@ fn nt_read_file(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState
|
||||
mem.write_bulk(buffer, slice);
|
||||
*position = start_pos + avail as u64;
|
||||
|
||||
tracing::info!(
|
||||
"NtReadFile: {} bytes from {:?} @ {} (handle={:#x})",
|
||||
avail, path, start_pos, handle,
|
||||
);
|
||||
{
|
||||
// STEP 51 — tag NtReadFile with the calling tid + cycle so the
|
||||
// window-prefetch thread can be correlated against tid24's demux read.
|
||||
let hw_id = state.scheduler.current_hw_id().unwrap_or(0);
|
||||
let rtid = state.scheduler.tid(hw_id).unwrap_or(0);
|
||||
tracing::info!(
|
||||
"NtReadFile: {} bytes from {:?} @ {} (handle={:#x}) tid={} cycle={}",
|
||||
avail, path, start_pos, handle, rtid, ctx.cycle_count,
|
||||
);
|
||||
}
|
||||
write_io_status_block(mem, io_status_block, STATUS_SUCCESS as u32, avail as u32);
|
||||
ctx.gpr[3] = STATUS_SUCCESS;
|
||||
signal_io_completion_event(state, event_handle);
|
||||
@@ -3187,6 +3193,20 @@ fn vd_swap(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
|
||||
let constants = xenia_gpu::xenos_constants::XenosConstantsBlock::snapshot(
|
||||
&gpu_inline.register_file,
|
||||
);
|
||||
if std::env::var("XENIA_CONST_LOG").is_ok() {
|
||||
let nz: Vec<usize> = constants
|
||||
.alu
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, v)| v.iter().any(|c| *c != 0.0))
|
||||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
eprintln!(
|
||||
"CONST-LOG alu_nonzero={} idxs={:?} c254={:?} c255={:?} c510={:?} c511={:?}",
|
||||
nz.len(), nz, constants.alu[254], constants.alu[255],
|
||||
constants.alu[510], constants.alu[511]
|
||||
);
|
||||
}
|
||||
ui.publish_assets(blobs, constants);
|
||||
|
||||
// P5b: publish the texture the last draw's *active pixel shader*
|
||||
@@ -4470,12 +4490,30 @@ fn nt_yield_execution(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut Ker
|
||||
ctx.gpr[3] = STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/// RE diagnostic (milestone-2 intro-video): `XENIA_LOG_RESUMES=1` makes
|
||||
/// `Ke/NtResumeThread` emit a `RESUME` line naming the resumed thread's
|
||||
/// tid, start_entry (so decode workers — entries 0x82506588/0x825065b8 —
|
||||
/// are identifiable), current pc and the prior suspend_count. Cold-path
|
||||
/// (resume is rare); inert unless the env is set. Read-only diagnostic.
|
||||
fn log_resumes_enabled() -> bool {
|
||||
use std::sync::OnceLock;
|
||||
static EN: OnceLock<bool> = OnceLock::new();
|
||||
*EN.get_or_init(|| std::env::var("XENIA_LOG_RESUMES").is_ok())
|
||||
}
|
||||
|
||||
fn ke_resume_thread(ctx: &mut PpcContext, _mem: &GuestMemory, state: &mut KernelState) {
|
||||
let raw = ctx.gpr[3] as u32;
|
||||
let handle = resolve_pseudo_handle(state, raw);
|
||||
match state.scheduler.find_by_handle(handle) {
|
||||
Some(r) => {
|
||||
state.scheduler.resume_ref(r);
|
||||
let prev = state.scheduler.resume_ref(r);
|
||||
if log_resumes_enabled() {
|
||||
let t = state.scheduler.thread(r);
|
||||
tracing::info!(
|
||||
"RESUME(Ke) tid={} start_entry={:#010x} pc={:#010x} prev_suspend={}",
|
||||
t.tid, t.start_entry, t.ctx.pc, prev,
|
||||
);
|
||||
}
|
||||
ctx.gpr[3] = STATUS_SUCCESS;
|
||||
}
|
||||
None => {
|
||||
@@ -4491,6 +4529,13 @@ fn nt_resume_thread(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelS
|
||||
match state.scheduler.find_by_handle(handle) {
|
||||
Some(r) => {
|
||||
let prev = state.scheduler.resume_ref(r);
|
||||
if log_resumes_enabled() {
|
||||
let t = state.scheduler.thread(r);
|
||||
tracing::info!(
|
||||
"RESUME(Nt) tid={} start_entry={:#010x} pc={:#010x} prev_suspend={}",
|
||||
t.tid, t.start_entry, t.ctx.pc, prev,
|
||||
);
|
||||
}
|
||||
if prev_ptr != 0 {
|
||||
mem.write_u32(prev_ptr, prev);
|
||||
}
|
||||
|
||||
@@ -909,6 +909,30 @@ impl KernelState {
|
||||
/// Record a Set/Pulse/Release/etc. call against a handle. `aux` is the
|
||||
/// previous signal state (or per-export-specific data).
|
||||
pub fn audit_signal(&mut self, handle: u32, lr: u32, source: &'static str, aux: u64) {
|
||||
// RE diagnostic (milestone-2 intro-video). `XENIA_LOG_SIGNAL=0x<va>`
|
||||
// (or `0xffffffff` for all) logs every event signal/pulse targeting
|
||||
// that object — to pin who, if anyone, signals the engine event
|
||||
// 0x40D10214 that tid23 waits on (the worker-resume gate). Read-only.
|
||||
{
|
||||
use std::sync::OnceLock;
|
||||
static SIG_T: OnceLock<u32> = OnceLock::new();
|
||||
let t = *SIG_T.get_or_init(|| {
|
||||
std::env::var("XENIA_LOG_SIGNAL")
|
||||
.ok()
|
||||
.and_then(|s| u32::from_str_radix(s.trim().trim_start_matches("0x"), 16).ok())
|
||||
.unwrap_or(0)
|
||||
});
|
||||
if t != 0 && (t == u32::MAX || t == handle) {
|
||||
let tid = self
|
||||
.scheduler
|
||||
.current_hw_id()
|
||||
.and_then(|h| self.scheduler.tid(h))
|
||||
.unwrap_or(0);
|
||||
tracing::info!(
|
||||
"SIGNAL src={source} handle={handle:#010x} tid={tid} lr={lr:#010x} prev_signaled={aux}"
|
||||
);
|
||||
}
|
||||
}
|
||||
if !self.audit.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user