[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:
@@ -2713,7 +2713,15 @@ fn worker_prologue(
|
||||
let c = kernel.scheduler.ctx(hw_id);
|
||||
[c.gpr[3], c.gpr[4], c.gpr[5], c.gpr[6]]
|
||||
};
|
||||
kernel.call_export(module, ordinal_u32, mem);
|
||||
{
|
||||
let _pt = xenia_gpu::prof::is_on().then(|| {
|
||||
xenia_gpu::prof::ScopeTimer::new(
|
||||
&xenia_gpu::prof::KERNEL_NS,
|
||||
&xenia_gpu::prof::KERNEL_CALLS,
|
||||
)
|
||||
});
|
||||
kernel.call_export(module, ordinal_u32, mem);
|
||||
}
|
||||
let post_ref = kernel.scheduler.current;
|
||||
let c = match post_ref {
|
||||
Some(r) => kernel.scheduler.ctx_mut_ref(r),
|
||||
@@ -2775,6 +2783,12 @@ fn worker_prologue(
|
||||
.current
|
||||
.expect("begin_slot_visit set scheduler.current to Some when slot has runnable thread");
|
||||
let block_ptr: *const xenia_cpu::block_cache::DecodedBlock = {
|
||||
let _pt = xenia_gpu::prof::is_on().then(|| {
|
||||
xenia_gpu::prof::ScopeTimer::new(
|
||||
&xenia_gpu::prof::BUILD_NS,
|
||||
&xenia_gpu::prof::BUILD_CALLS,
|
||||
)
|
||||
});
|
||||
let pc_for_lookup = kernel.scheduler.ctx(hw_id).pc;
|
||||
let b: &xenia_cpu::block_cache::DecodedBlock =
|
||||
wc.block_cache.lookup_or_build(pc_for_lookup, mem);
|
||||
@@ -3112,6 +3126,7 @@ fn run_superblock(
|
||||
let cycle_before = kernel.scheduler.ctx_mut_ref(thread_ref).cycle_count;
|
||||
let mmio_before = mem.mmio_access_count();
|
||||
let block = unsafe { &*block_ptr };
|
||||
let _prof_t0 = xenia_gpu::prof::is_on().then(std::time::Instant::now);
|
||||
let result = {
|
||||
let ctx = kernel.scheduler.ctx_mut_ref(thread_ref);
|
||||
step_block(ctx, mem, block)
|
||||
@@ -3121,6 +3136,13 @@ fn run_superblock(
|
||||
.ctx_mut_ref(thread_ref)
|
||||
.cycle_count
|
||||
.saturating_sub(cycle_before);
|
||||
if let Some(t0) = _prof_t0 {
|
||||
use xenia_gpu::prof;
|
||||
prof::add(&prof::STEP_NS, t0.elapsed().as_nanos() as u64);
|
||||
prof::add(&prof::STEP_INSTR, executed);
|
||||
prof::add(&prof::STEP_CALLS, 1);
|
||||
prof::maybe_report_by_instr();
|
||||
}
|
||||
total_executed = total_executed.saturating_add(executed);
|
||||
|
||||
// STOP conditions (any → end the superblock, hand to epilogue):
|
||||
@@ -3161,7 +3183,15 @@ fn run_superblock(
|
||||
// invalidates the previous `block_ptr` — but we've already finished
|
||||
// using it (only `sync_sensitive`/diagnostics were read, above), so
|
||||
// the raw-pointer aliasing rule is respected.
|
||||
block_ptr = wc.block_cache.lookup_or_build(next_pc, mem) as *const _;
|
||||
{
|
||||
let _pt = xenia_gpu::prof::is_on().then(|| {
|
||||
xenia_gpu::prof::ScopeTimer::new(
|
||||
&xenia_gpu::prof::BUILD_NS,
|
||||
&xenia_gpu::prof::BUILD_CALLS,
|
||||
)
|
||||
});
|
||||
block_ptr = wc.block_cache.lookup_or_build(next_pc, mem) as *const _;
|
||||
}
|
||||
};
|
||||
|
||||
worker_epilogue(
|
||||
@@ -3610,10 +3640,19 @@ fn run_execution_parallel(
|
||||
|
||||
// ── unlocked window ───────────────
|
||||
let block = unsafe { &*block_ptr };
|
||||
let _prof_t0 =
|
||||
xenia_gpu::prof::is_on().then(std::time::Instant::now);
|
||||
let result = step_block(&mut ctx_taken, mem_ref, block);
|
||||
let executed = ctx_taken
|
||||
.cycle_count
|
||||
.saturating_sub(cycle_before);
|
||||
if let Some(t0) = _prof_t0 {
|
||||
use xenia_gpu::prof;
|
||||
prof::add(&prof::STEP_NS, t0.elapsed().as_nanos() as u64);
|
||||
prof::add(&prof::STEP_INSTR, executed);
|
||||
prof::add(&prof::STEP_CALLS, 1);
|
||||
prof::maybe_report_by_instr();
|
||||
}
|
||||
// ──────────────────────────────────
|
||||
|
||||
let mut guard = kernel_w.lock().expect("kernel mutex poisoned");
|
||||
@@ -4305,6 +4344,11 @@ fn dump_thread_diagnostic(
|
||||
}
|
||||
use xenia_kernel::objects::KernelObject;
|
||||
|
||||
// Probe-patch (UNCOMMITTED): env-gated wall-time profile of the run.
|
||||
if xenia_gpu::prof::enabled() {
|
||||
xenia_gpu::prof::report(0);
|
||||
}
|
||||
|
||||
// STEP-10 diagnostic (observe-only, env-gated `XENIA_DUMP_SLOTS=1`).
|
||||
// Prints each scheduler slot's full runqueue with the fields needed to
|
||||
// distinguish "Blocked(Suspended) forever" from "Ready but never picked":
|
||||
|
||||
Reference in New Issue
Block a user