ITERATE-2.V: scheduler priority aging closes 18-day AUDIT-049 wedge

Priority aging in xenia-cpu/scheduler.rs:pick_runnable
(effective_priority = base + age_bonus(now_round - last_run_round),
capped at +31, AGING_ROUNDS_PER_BONUS=1). Strict-priority was parking
priority=0 threads behind CPU-bound priority=15 audio mixer
(sub_824D1328 guest spinwait at PC=0x824d1404 on CPU5). Aging
eventually picks the starved thread, breaking the producer-consumer
cycle that caused 5-tid wedge at PC=0x824ac578 since AUDIT-049 (10 May).

Cascade observed: tid=13 clean exit; events 121K -> 13M (107x); last
host_ns 767ms -> 51,011ms (66x); 8 new threads spawn; VdSwap 1 -> 2.

Complete two-day iterate sequence (2026-05-27 -> 2026-05-28):
- 2.F: VdSwap drain timeout 900ms -> 1ms (xenia-gpu/handle.rs); 876x
       perf win on VdSwap kernel callback
- 2.H: vA0000000 physical heap bucket added (state.rs, exports.rs);
       ctx_ptrs now in 0xA0000000-0xBFFFFFFF range matching canary
- 2.L: Phase-A diff harness categorized [return_value mismatch],
       [status mismatch], [args_resolved.path mismatch] tags
       (tools/diff-events/diff_events.py); closes reading-error #41
       (silent test-harness state leak invalidating trace diffs)
- 2.M: always-on exit-thread-state.json sibling to Phase-A JSONL
       (event_log.rs + xenia-app/main.rs); closes reading-error #42
       (Phase-A blind to blocked-forever waits)
- 2.Q: signal.match kernel instrumentation in NtSetEvent /
       NtReleaseSemaphore / KeSetEvent / KeReleaseSemaphore
       (exports.rs); emits target_handle + waiter_count + waiter_tids
- 2.T: wake.requested kernel instrumentation in wake_eligible_waiters
       (exports.rs); emits target_tid + transition + new_state
- 2.V: scheduler priority aging (xenia-cpu/scheduler.rs) [keystone]

Plus accumulated WIP from earlier May (contention_manifest,
phase_b_snapshot, xam/xaudio enhancements, analysis db, xex loader,
xenia-app main loop, etc.). Audit-runs/ artifacts remain untracked
per project convention.

Tests: 300 xenia-cpu / 227 xenia-kernel / 5 xenia-app / 19 xenia-path
/ 30+ smaller suites -- all PASS, 0 regressions. Determinism preserved
(2x cold runs bit-identical at 13,003,881 events post-2.V).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-29 07:27:26 +02:00
parent e6d43a23ac
commit ad45873a1b
50 changed files with 14389 additions and 506 deletions

View File

@@ -897,17 +897,12 @@ fn insert_functions(
func_analysis: &FuncAnalysis,
labels: &HashMap<u32, String>,
) -> anyhow::Result<()> {
let mut stmt = conn.prepare(
"INSERT INTO functions
(address, name, end_address, frame_size, saved_gprs, is_leaf, is_saverestore,
pdata_validated, pdata_length, has_eh)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
)?;
let mut app = conn.appender("functions")?;
for (&addr, fi) in &func_analysis.functions {
let name = labels.get(&addr)
.cloned()
.unwrap_or_else(|| format!("sub_{addr:08X}"));
stmt.execute(params![
app.append_row(params![
addr as i64,
name,
fi.end as i64,
@@ -920,6 +915,7 @@ fn insert_functions(
fi.has_eh,
])?;
}
app.flush()?;
Ok(())
}
@@ -930,15 +926,13 @@ fn insert_vtables(
_image_base: u32,
) -> anyhow::Result<()> {
if vtables.is_empty() { return Ok(()); }
let mut stmt = conn.prepare(
"INSERT INTO vtables
(address, length, col_address, class_name, rtti_present, base_classes_json)
VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT DO NOTHING"
)?;
let mut count = 0u64;
let mut dedup: HashMap<u32, &crate::vtables::Vtable> = HashMap::new();
for v in vtables {
stmt.execute(params![
dedup.entry(v.address).or_insert(v);
}
let mut app = conn.appender("vtables")?;
for v in dedup.values() {
app.append_row(params![
v.address as i64,
v.length as i64,
v.col_address.map(|a| a as i64),
@@ -946,8 +940,9 @@ fn insert_vtables(
v.rtti_present,
v.base_classes_json.as_deref(),
])?;
count += 1;
}
app.flush()?;
let count = dedup.len() as u64;
metrics::counter!("db.rows", "table" => "vtables").increment(count);
tracing::info!(rows = count, table = "vtables", "bulk insert complete");
Ok(())
@@ -960,17 +955,17 @@ fn insert_methods_and_classes(
) -> anyhow::Result<()> {
if vtables.is_empty() { return Ok(()); }
// methods rows
// methods rows — dedup by (vtable_address, slot), first-write-wins.
let methods = crate::vtables::methods_table(vtables, labels);
if !methods.is_empty() {
let mut stmt = conn.prepare(
"INSERT INTO methods
(vtable_address, slot, function_address, mangled_name, demangled_name)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT DO NOTHING"
)?;
for (vt_addr, slot, fn_addr, mangled, demangled) in &methods {
stmt.execute(params![
let mut idx: HashMap<(u32, u32), usize> = HashMap::new();
for (i, m) in methods.iter().enumerate() {
idx.entry((m.0, m.1)).or_insert(i);
}
let mut app = conn.appender("methods")?;
for &i in idx.values() {
let (vt_addr, slot, fn_addr, mangled, demangled) = &methods[i];
app.append_row(params![
*vt_addr as i64,
*slot as i64,
*fn_addr as i64,
@@ -978,29 +973,33 @@ fn insert_methods_and_classes(
demangled.as_deref(),
])?;
}
metrics::counter!("db.rows", "table" => "methods").increment(methods.len() as u64);
tracing::info!(rows = methods.len(), table = "methods", "bulk insert complete");
app.flush()?;
let n = idx.len() as u64;
metrics::counter!("db.rows", "table" => "methods").increment(n);
tracing::info!(rows = n, table = "methods", "bulk insert complete");
}
// classes rows (deduped by class_name, first-detected wins)
// classes rows dedup by class_name, first-detected wins.
let classes = crate::vtables::classes_table(vtables);
if !classes.is_empty() {
let mut stmt = conn.prepare(
"INSERT INTO classes
(name, vtable_address, rtti_present, base_classes_json)
VALUES (?, ?, ?, ?)
ON CONFLICT DO NOTHING"
)?;
for (name, vt_addr, rtti, bases) in &classes {
stmt.execute(params![
let mut idx: HashMap<&str, usize> = HashMap::new();
for (i, c) in classes.iter().enumerate() {
idx.entry(c.0.as_str()).or_insert(i);
}
let mut app = conn.appender("classes")?;
for &i in idx.values() {
let (name, vt_addr, rtti, bases) = &classes[i];
app.append_row(params![
name.as_str(),
*vt_addr as i64,
*rtti,
bases.as_deref(),
])?;
}
metrics::counter!("db.rows", "table" => "classes").increment(classes.len() as u64);
tracing::info!(rows = classes.len(), table = "classes", "bulk insert complete");
app.flush()?;
let n = idx.len() as u64;
metrics::counter!("db.rows", "table" => "classes").increment(n);
tracing::info!(rows = n, table = "classes", "bulk insert complete");
}
Ok(())
@@ -1011,20 +1010,21 @@ fn insert_strings(
strings: &[crate::strings::DetectedString],
) -> anyhow::Result<()> {
if strings.is_empty() { return Ok(()); }
let mut stmt = conn.prepare(
"INSERT INTO strings (address, encoding, length, content) VALUES (?, ?, ?, ?)
ON CONFLICT DO NOTHING"
)?;
let mut count = 0u64;
let mut dedup: HashMap<u32, &crate::strings::DetectedString> = HashMap::new();
for s in strings {
stmt.execute(params![
dedup.entry(s.address).or_insert(s);
}
let mut app = conn.appender("strings")?;
for s in dedup.values() {
app.append_row(params![
s.address as i64,
s.encoding,
s.length as i64,
s.content.as_str(),
])?;
count += 1;
}
app.flush()?;
let count = dedup.len() as u64;
metrics::counter!("db.rows", "table" => "strings").increment(count);
tracing::info!(rows = count, table = "strings", "bulk insert complete");
Ok(())
@@ -1035,31 +1035,17 @@ fn insert_eh_records(
records: &[crate::eh_scope::EhFuncInfo],
) -> anyhow::Result<()> {
if records.is_empty() { return Ok(()); }
let mut stmt_fi = conn.prepare(
"INSERT INTO eh_funcinfo
(address, magic, max_state, p_unwind_map, n_try_blocks,
p_try_block_map, n_ip_map_entries, p_ip_to_state_map,
p_es_type_list, eh_flags)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT DO NOTHING"
)?;
let mut stmt_unwind = conn.prepare(
"INSERT INTO eh_unwind_map
(funcinfo_address, state_index, to_state, action_pc)
VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING"
)?;
let mut stmt_try = conn.prepare(
"INSERT INTO eh_try_blocks
(funcinfo_address, try_index, try_low, try_high, catch_high,
n_catches, p_handler_array)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT DO NOTHING"
)?;
let mut n_fi = 0u64;
let mut n_unwind = 0u64;
let mut n_try = 0u64;
for r in records {
stmt_fi.execute(params![
// Dedup eh_funcinfo by PK (address), first-write-wins.
// Within a deduped record, unwind/try entries are uniquely indexed by enumerate.
let mut fi_idx: HashMap<u32, usize> = HashMap::new();
for (i, r) in records.iter().enumerate() {
fi_idx.entry(r.address).or_insert(i);
}
let mut app_fi = conn.appender("eh_funcinfo")?;
for &i in fi_idx.values() {
let r = &records[i];
app_fi.append_row(params![
r.address as i64, r.magic as i64, r.max_state as i64,
r.p_unwind_map as i64, r.n_try_blocks as i64,
r.p_try_block_map as i64, r.n_ip_map_entries as i64,
@@ -1067,22 +1053,38 @@ fn insert_eh_records(
r.p_es_type_list.map(|p| p as i64),
r.eh_flags.map(|f| f as i64),
])?;
n_fi += 1;
for (i, e) in r.unwind_map.iter().enumerate() {
stmt_unwind.execute(params![
r.address as i64, i as i64, e.to_state as i64, e.action_pc as i64,
}
app_fi.flush()?;
let n_fi = fi_idx.len() as u64;
let mut app_unwind = conn.appender("eh_unwind_map")?;
let mut n_unwind = 0u64;
for &i in fi_idx.values() {
let r = &records[i];
for (j, e) in r.unwind_map.iter().enumerate() {
app_unwind.append_row(params![
r.address as i64, j as i64, e.to_state as i64, e.action_pc as i64,
])?;
n_unwind += 1;
}
for (i, t) in r.try_blocks.iter().enumerate() {
stmt_try.execute(params![
r.address as i64, i as i64,
}
app_unwind.flush()?;
let mut app_try = conn.appender("eh_try_blocks")?;
let mut n_try = 0u64;
for &i in fi_idx.values() {
let r = &records[i];
for (j, t) in r.try_blocks.iter().enumerate() {
app_try.append_row(params![
r.address as i64, j as i64,
t.try_low as i64, t.try_high as i64, t.catch_high as i64,
t.n_catches as i64, t.p_handler_array as i64,
])?;
n_try += 1;
}
}
app_try.flush()?;
metrics::counter!("db.rows", "table" => "eh_funcinfo").increment(n_fi);
metrics::counter!("db.rows", "table" => "eh_unwind_map").increment(n_unwind);
metrics::counter!("db.rows", "table" => "eh_try_blocks").increment(n_try);
@@ -1097,54 +1099,55 @@ fn insert_typed_ind_dispatch(
conn: &Connection,
t: &crate::ind_dispatch_typed::TypedIndirectResult,
) -> anyhow::Result<()> {
// Dedupe by PK before appending: the Appender writes directly to columnar
// storage and does not enforce primary keys, so duplicates here would
// surface as PK violations at flush. First-write-wins matches the previous
// `ON CONFLICT DO NOTHING` behaviour.
if !t.dispatches.is_empty() {
let mut stmt_site = conn.prepare(
"INSERT INTO indirect_dispatch_sites
(dispatch_pc, vptr_offset, slot, candidate_count)
VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING"
)?;
let mut stmt_cand = conn.prepare(
"INSERT INTO indirect_dispatch_candidates
(dispatch_pc, vtable_address, method_address)
VALUES (?, ?, ?) ON CONFLICT DO NOTHING"
)?;
let mut n_sites = 0u64;
let mut n_cand = 0u64;
let mut sites: HashMap<u32, (u32, u32, usize)> = HashMap::new();
let mut cands: HashMap<(u32, u32), u32> = HashMap::new();
for d in &t.dispatches {
stmt_site.execute(params![
d.dispatch_pc as i64,
d.vptr_offset as i64,
d.slot as i64,
d.candidate_vtables.len() as i64,
])?;
n_sites += 1;
sites.entry(d.dispatch_pc)
.or_insert((d.vptr_offset, d.slot, d.candidate_vtables.len()));
for (vt, m) in d.candidate_vtables.iter().zip(d.method_pcs.iter()) {
stmt_cand.execute(params![
d.dispatch_pc as i64, *vt as i64, *m as i64,
])?;
n_cand += 1;
cands.entry((d.dispatch_pc, *vt)).or_insert(*m);
}
}
let mut app_sites = conn.appender("indirect_dispatch_sites")?;
for (pc, (off, slot, count)) in &sites {
app_sites.append_row(params![
*pc as i64, *off as i64, *slot as i64, *count as i64,
])?;
}
app_sites.flush()?;
let mut app_cand = conn.appender("indirect_dispatch_candidates")?;
for ((pc, vt), m) in &cands {
app_cand.append_row(params![*pc as i64, *vt as i64, *m as i64])?;
}
app_cand.flush()?;
let n_sites = sites.len() as u64;
let n_cand = cands.len() as u64;
metrics::counter!("db.rows", "table" => "indirect_dispatch_sites").increment(n_sites);
metrics::counter!("db.rows", "table" => "indirect_dispatch_candidates").increment(n_cand);
tracing::info!(sites = n_sites, candidates = n_cand, "typed indirect-dispatch insert complete");
}
if !t.vptr_writes.is_empty() {
let mut stmt = conn.prepare(
"INSERT INTO vptr_writes
(writer_pc, vtable_address, vptr_offset, writer_function)
VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING"
)?;
let mut n = 0u64;
let mut writes: HashMap<(u32, u32, u32), u32> = HashMap::new();
for w in &t.vptr_writes {
stmt.execute(params![
w.writer_pc as i64,
w.vtable_addr as i64,
w.vptr_offset as i64,
w.writer_function as i64,
])?;
n += 1;
writes.entry((w.writer_pc, w.vtable_addr, w.vptr_offset))
.or_insert(w.writer_function);
}
let mut app = conn.appender("vptr_writes")?;
for ((wpc, vt, off), wf) in &writes {
app.append_row(params![
*wpc as i64, *vt as i64, *off as i64, *wf as i64,
])?;
}
app.flush()?;
let n = writes.len() as u64;
metrics::counter!("db.rows", "table" => "vptr_writes").increment(n);
tracing::info!(rows = n, "vptr_writes insert complete");
}
@@ -1156,26 +1159,31 @@ fn insert_funcptr_arrays(
arrays: &[crate::funcptr_arrays::FuncPtrArray],
) -> anyhow::Result<()> {
if arrays.is_empty() { return Ok(()); }
let mut stmt_arr = conn.prepare(
"INSERT INTO function_pointer_arrays (address, length, kind) VALUES (?, ?, ?)
ON CONFLICT DO NOTHING"
)?;
let mut stmt_ent = conn.prepare(
"INSERT INTO function_pointer_array_entries (array_address, slot, function_address)
VALUES (?, ?, ?) ON CONFLICT DO NOTHING"
)?;
let mut n_arr = 0u64;
// Dedup arrays by PK (address), first-write-wins.
let mut idx: HashMap<u32, usize> = HashMap::new();
for (i, a) in arrays.iter().enumerate() {
idx.entry(a.address).or_insert(i);
}
let mut app_arr = conn.appender("function_pointer_arrays")?;
for &i in idx.values() {
let a = &arrays[i];
app_arr.append_row(params![a.address as i64, a.length as i64, a.kind])?;
}
app_arr.flush()?;
let n_arr = idx.len() as u64;
let mut app_ent = conn.appender("function_pointer_array_entries")?;
let mut n_ent = 0u64;
for a in arrays {
let inserted = stmt_arr.execute(params![
a.address as i64, a.length as i64, a.kind,
])?;
if inserted > 0 { n_arr += 1; }
for (i, &fn_va) in a.entries.iter().enumerate() {
stmt_ent.execute(params![a.address as i64, i as i64, fn_va as i64])?;
for &i in idx.values() {
let a = &arrays[i];
for (slot, &fn_va) in a.entries.iter().enumerate() {
app_ent.append_row(params![a.address as i64, slot as i64, fn_va as i64])?;
n_ent += 1;
}
}
app_ent.flush()?;
metrics::counter!("db.rows", "table" => "function_pointer_arrays").increment(n_arr);
metrics::counter!("db.rows", "table" => "function_pointer_array_entries").increment(n_ent);
tracing::info!(arrays = n_arr, entries = n_ent, "function-pointer arrays insert complete");
@@ -1187,13 +1195,8 @@ fn insert_demangled_from_labels(
labels: &HashMap<u32, String>,
import_libraries: &[xenia_xex::header::ImportLibrary],
) -> anyhow::Result<()> {
let mut stmt = conn.prepare(
"INSERT INTO demangled_names
(address, mangled, raw_demangled, namespace_path, class_name,
method_name, params_signature)
VALUES (?, ?, ?, ?, ?, ?, ?)"
)?;
// demangled_names has no PK — straight append, no dedup needed.
let mut app = conn.appender("demangled_names")?;
let mut count = 0u64;
for (&addr, name) in labels {
@@ -1206,7 +1209,7 @@ fn insert_demangled_from_labels(
continue;
}
if let Some(d) = crate::demangle::demangle(name) {
stmt.execute(params![
app.append_row(params![
addr as i64,
d.mangled,
d.raw_demangled,
@@ -1226,7 +1229,7 @@ fn insert_demangled_from_labels(
if let Some(name) = resolved
&& let Some(d) = crate::demangle::demangle(name)
{
stmt.execute(params![
app.append_row(params![
imp.address as i64,
d.mangled,
d.raw_demangled,
@@ -1240,6 +1243,7 @@ fn insert_demangled_from_labels(
}
}
app.flush()?;
metrics::counter!("db.rows", "table" => "demangled_names").increment(count);
tracing::info!(rows = count, table = "demangled_names", "demangler complete");
Ok(())
@@ -1252,14 +1256,15 @@ fn insert_pdata_entries(
if entries.is_empty() {
return Ok(());
}
let mut stmt = conn.prepare(
"INSERT INTO pdata_entries
(begin_address, end_address, function_length, prolog_length, flags)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT DO NOTHING"
)?;
for e in entries {
stmt.execute(params![
// Dedup by PK (begin_address), first-write-wins.
let mut idx: HashMap<u32, usize> = HashMap::new();
for (i, e) in entries.iter().enumerate() {
idx.entry(e.begin_address).or_insert(i);
}
let mut app = conn.appender("pdata_entries")?;
for &i in idx.values() {
let e = &entries[i];
app.append_row(params![
e.begin_address as i64,
e.end_address() as i64,
e.function_length as i64,
@@ -1267,6 +1272,7 @@ fn insert_pdata_entries(
e.flags as i64,
])?;
}
app.flush()?;
Ok(())
}
@@ -1274,9 +1280,8 @@ fn insert_labels(
conn: &Connection,
labels: &HashMap<u32, String>,
) -> anyhow::Result<()> {
let mut stmt = conn.prepare(
"INSERT INTO labels (address, name, kind) VALUES (?, ?, ?) ON CONFLICT DO NOTHING"
)?;
// Source is a HashMap so addresses are unique by construction — no dedup needed.
let mut app = conn.appender("labels")?;
for (&addr, name) in labels {
let kind = if name.starts_with("sub_") || name == "entry_point" {
"function"
@@ -1291,8 +1296,9 @@ fn insert_labels(
} else {
"other"
};
stmt.execute(params![addr as i64, name, kind])?;
app.append_row(params![addr as i64, name, kind])?;
}
app.flush()?;
Ok(())
}