From 48e166579b0bcb93e69c6ef60b58467488b14e37 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sun, 5 Jul 2026 20:18:28 +0200 Subject: [PATCH] [iterate-4E] Stage 0: native-threads mode flag + functional gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scaffolding for the canary-model rework (host-thread-per-guest-thread, opt-in, non-deterministic). No behavior change with the flag off. - XENIA_NATIVE_THREADS=1 selects the new executor; folded into the parallel spawn gate (reuses Arc> + worker dispatch). - native_threads_enabled() = single source of truth for guarded branches. - run_execution_native(): Stage-0 body delegates to the iterate-4D free-run executor verbatim (native == freerun for now); Stage 1' will replace the worker set with one host thread per guest thread. - native-gate.sh: functional oracle replacing byte-goldens for the MT path — (1) lockstep golden byte-identity (flag-off safety net), (2) native render milestone (draws>0 && swaps>0), (3) native deadlock stress. GREEN 3/3 on current code. Co-Authored-By: Claude Opus 4.8 --- crates/xenia-app/src/main.rs | 91 +++++++++++++++++++++++++++++++++++- native-gate.sh | 87 ++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 1 deletion(-) create mode 100755 native-gate.sh diff --git a/crates/xenia-app/src/main.rs b/crates/xenia-app/src/main.rs index 980c015..420c64f 100644 --- a/crates/xenia-app/src/main.rs +++ b/crates/xenia-app/src/main.rs @@ -1854,7 +1854,18 @@ fn cmd_exec_inner( let v = v.trim().to_ascii_lowercase(); v == "1" || v == "true" || v == "yes" }); - let do_parallel = parallel || env_parallel; + // iterate-4E: canary-model native-threads mode (host-thread-per- + // guest-thread). Opt-in; reuses the parallel spawn substrate + // (kernel in Arc>, worker-thread dispatch). `run_execution_ + // parallel` routes it to `run_execution_native`. Flag-off leaves the + // lockstep golden path byte-identical. + let env_native = std::env::var("XENIA_NATIVE_THREADS") + .ok() + .is_some_and(|v| { + let v = v.trim().to_ascii_lowercase(); + v == "1" || v == "true" || v == "yes" + }); + let do_parallel = parallel || env_parallel || env_native; // Step 04 gate: --parallel runs N=6 workers that release // the kernel mutex around step_block, so the per-instruction // observation path (debugger hooks, DB writer, force-per-instr) @@ -4069,6 +4080,25 @@ fn run_execution_parallel( // the workload exposes needs fine-grained kernel locking (the single // Arc> serializes the 6 workers); until then the default // --parallel path stays the per-round barrier executor below. + // iterate-4E: canary-model native-threads mode. Routed here ahead of the + // freerun check because native mode is the evolution of the freerun + // executor (Stage 0 delegates to it verbatim; Stage 1' replaces the worker + // set with one host thread per guest thread). Opt-in via XENIA_NATIVE_THREADS. + if native_threads_enabled() { + return run_execution_native( + mem, + kernel_arc, + debugger, + thunk_map, + db_writer, + max_instructions, + ips_limit, + quiet, + halt_on_deadlock, + shutdown_outer, + ); + } + if std::env::var("XENIA_PARALLEL_FREERUN") .map(|v| v == "1" || v.eq_ignore_ascii_case("true")) .unwrap_or(false) @@ -4559,6 +4589,65 @@ mod freerun_prof { /// Non-deterministic by design (thread interleaving); this is the opt-in perf /// mode. Lockstep (`run_execution`) remains the byte-identical golden path. #[allow(clippy::too_many_arguments)] +/// iterate-4E: is the canary-model native-threads mode selected? +/// `XENIA_NATIVE_THREADS=1`. Single source of truth so every guarded native +/// branch agrees; when unset the emulator behaves exactly as before (the +/// lockstep golden path and the existing `--parallel`/freerun paths are +/// untouched). +fn native_threads_enabled() -> bool { + std::env::var("XENIA_NATIVE_THREADS") + .map(|v| { + let v = v.trim().to_ascii_lowercase(); + v == "1" || v == "true" || v == "yes" + }) + .unwrap_or(false) +} + +/// iterate-4E — canary-model native-threads executor entry. +/// +/// Target end state (across Stages 1'–4'): one real host OS thread per guest +/// thread, free-running, blocking on host primitives at guest waits, wall-clock +/// timebase — i.e. xenia-canary's model, opt-in and non-deterministic, with the +/// lockstep path retained as the deterministic golden/reference mode. +/// +/// STAGE 0 (this commit): pure scaffolding. The flag is wired end-to-end and +/// routed here, but the body delegates to the proven `iterate-4D` free-run +/// executor verbatim, so native mode == freerun for now. This lets the +/// functional gate (milestone boot + stress + deadlock check) run green against +/// the flag before the executor is transformed in Stage 1'. Nothing in the +/// default (flag-off) path changes. +fn run_execution_native( + mem: &xenia_memory::GuestMemory, + kernel_arc: &std::sync::Arc>, + debugger: &mut xenia_debugger::Debugger, + thunk_map: &HashMap, + db_writer: Option<&mut xenia_analysis::DbWriter>, + max_instructions: Option, + ips_limit: Option, + quiet: bool, + halt_on_deadlock: bool, + shutdown_outer: Option>, +) -> ExecStats { + if !quiet { + info!( + "iterate-4E Stage 0: native-threads mode selected (delegating to \ + free-run executor; per-guest-thread model lands in Stage 1')" + ); + } + run_execution_parallel_freerun( + mem, + kernel_arc, + debugger, + thunk_map, + db_writer, + max_instructions, + ips_limit, + quiet, + halt_on_deadlock, + shutdown_outer, + ) +} + fn run_execution_parallel_freerun( mem: &xenia_memory::GuestMemory, kernel_arc: &std::sync::Arc>, diff --git a/native-gate.sh b/native-gate.sh new file mode 100755 index 0000000..4bd24ba --- /dev/null +++ b/native-gate.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# native-gate.sh — functional correctness gate for the iterate-4E canary-model +# native-threads rework (host-thread-per-guest-thread, non-deterministic). +# +# Byte-identical goldens cannot gate a multi-threaded run (OS interleaving is +# nondeterministic), so this script is the REPLACEMENT oracle. It runs three +# checks and exits 0 iff all pass: +# +# 1. LOCKSTEP GOLDEN (flag-off): the deterministic default path stays +# byte-identical on sylpheed_n200m.json. This is the safety net that +# proves the native-mode work did not disturb the reference path. +# 2. NATIVE RENDER MILESTONE: native mode (XENIA_NATIVE_THREADS=1) boots far +# enough to render — draws>0 && swaps>0 in the run digest — proving the +# executor runs guest CPU + drives the GPU end-to-end without hanging. +# 3. NATIVE DEADLOCK STRESS: parallel_stress_short under XENIA_NATIVE_THREADS=1 +# with --halt-on-deadlock; N back-to-back short runs with no panic/hang, +# surfacing lost-wakeups / lock-order inversions the single run misses. +# +# Usage: [SYLPHEED_ISO=...] native-gate.sh [milestone_n] [milestone_timeout_s] +# milestone_n default 200000000 (renders; matches the golden anchor) +# milestone_timeout_s default 120 +# +# The binary must be built first (the caller owns the build so the OOM guardrail +# CARGO_BUILD_JOBS=4 / free-check stays explicit): +# CARGO_BUILD_JOBS=4 cargo build --release +set -u +cd "$(dirname "$0")" || exit 2 +BIN=./target/release/xenia-rs +ISO_CHECK=sylpheed.iso +MN="${1:-200000000}" +MTO="${2:-120}" +DIGEST=/tmp/native-gate-digest.json +LOG=/tmp/native-gate +mkdir -p "$LOG" +fails=0 +hr(){ printf '=%.0s' {1..64}; echo; } + +[ -x "$BIN" ] || { echo "FAIL: build first: CARGO_BUILD_JOBS=4 cargo build --release"; exit 3; } + +# ---------------------------------------------------------------- 1) golden +hr; echo "[1/3] LOCKSTEP GOLDEN — flag-off byte-identity (sylpheed_n200m)"; hr +cargo test --release -p xenia-app --test sylpheed_oracles -- \ + --ignored --nocapture sylpheed_n200m >"$LOG/golden.log" 2>&1 +rc=$? +if [ $rc -eq 0 ]; then echo " PASS (golden byte-identical)"; else + echo " FAIL rc=$rc — see $LOG/golden.log"; tail -20 "$LOG/golden.log"; fails=$((fails+1)); fi + +# ------------------------------------------------------- 2) native milestone +hr; echo "[2/3] NATIVE RENDER MILESTONE — XENIA_NATIVE_THREADS=1, -n $MN"; hr +rm -f "$DIGEST" +XENIA_NATIVE_THREADS=1 timeout "$MTO" "$BIN" check "$ISO_CHECK" \ + -n "$MN" --gpu-inline --out "$DIGEST" >"$LOG/milestone.log" 2>&1 +rc=$? +pkill -x xenia-rs 2>/dev/null +if [ $rc -ne 0 ]; then + echo " FAIL emulator rc=$rc (timeout=$MTO s) — see $LOG/milestone.log" + tail -20 "$LOG/milestone.log"; fails=$((fails+1)) +elif [ ! -f "$DIGEST" ]; then + echo " FAIL no digest written — see $LOG/milestone.log"; fails=$((fails+1)) +else + read -r draws swaps instrs < <(python3 - "$DIGEST" <<'PY' +import json,sys +d=json.load(open(sys.argv[1])) +print(d.get("draws",0), d.get("swaps",0), d.get("instructions",0)) +PY +) + echo " digest: instructions=$instrs draws=$draws swaps=$swaps" + if [ "${draws:-0}" -gt 0 ] && [ "${swaps:-0}" -gt 0 ]; then + echo " PASS (native mode renders)" + else + echo " FAIL (native mode did not render: draws=$draws swaps=$swaps)"; fails=$((fails+1)); fi +fi + +# ---------------------------------------------------------- 3) native stress +hr; echo "[3/3] NATIVE DEADLOCK STRESS — parallel_stress_short (native)"; hr +XENIA_NATIVE_THREADS=1 cargo test --release -p xenia-app --test parallel_stress -- \ + --nocapture parallel_stress_short >"$LOG/stress.log" 2>&1 +rc=$? +if [ $rc -eq 0 ]; then + grep -o "runs=[0-9]* ok=[0-9]* failed=[0-9]*" "$LOG/stress.log" | tail -1 + echo " PASS (no deadlock/panic)" +else + echo " FAIL rc=$rc — see $LOG/stress.log"; tail -20 "$LOG/stress.log"; fails=$((fails+1)); fi + +hr +if [ "$fails" -eq 0 ]; then echo "NATIVE GATE: PASS (3/3)"; exit 0 +else echo "NATIVE GATE: FAIL ($fails/3 checks failed)"; exit 1; fi