Bundles state that lives OUTSIDE the xenia-rs repo so a fresh clone on
another machine can be brought up to identical configuration via
migration/setup.sh:
- claude-memory/ ~/.claude/projects/-home-fabi-RE-Project-Sylpheed/memory/
(103 files, 1.1 MB - MEMORY.md + every
project_xenia_rs_*.md from audits
addis_signext through audit-058)
- project-root/dot-claude/ <project-root>/.claude/settings.json
(Stop hook + permissions)
- project-root/ppc-manual/ <project-root>/ppc-manual/
(PowerPC reference docs, 397 files, 3.7 MB)
- project-root/run-canary.sh <project-root>/run-canary.sh
- README.md Human-readable setup checklist
- setup.sh Idempotent installer (also reclones
xenia-canary at pinned HEAD 6de80dffe)
- MANIFEST.md Per-file mapping + per-file-not-bundled
restoration recipe
Excluded from bundle (not shippable via git):
- Sylpheed ISO (7.8 GB; copyright; manual copy required)
- sylpheed.db (395 MB; regenerable from XEX via analysis tooling)
- target/ build artifacts (rebuild on target)
- audit-runs probe firehoses (.log/.stdout/.stderr ~11 GB; rerun if needed)
- audit-runs memory dumps (.bin ~4.5 GB; rerun audit-026/027/029 if needed)
- xenia-canary checkout (setup.sh reclones from
git.mc02.dev/fabi/Xenia-Canary.git at HEAD 6de80dffe)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.9 KiB
name, description, type, originSessionId
| name | description | type | originSessionId |
|---|---|---|---|
| xenia-rs scheduler architecture (post-Axis-1-to-5 refactor, 2026-04-23) | Canonical scheduler model — 6 HW slots × per-slot priority runqueues, single host thread, GuestThread as first-class, ThreadRef identity, bind-and-migrate affinity. Supersedes the old HwThread[32] one-thread-per-slot model. | project | a178fdd6-2965-4652-903a-f684cf80835d |
Model in one paragraph
Single host thread runs the interpreter (GuestMemory pinned). Scheduler has 6 HwSlots matching Xenon hardware. Each slot holds runqueue: Vec<GuestThread> + running_idx: Option<usize>. A GuestThread owns its own PpcContext inline — the live register file is always the one on whichever thread the slot has pinned as running, so context switch is just a running_idx flip (no memcpy). Unlimited guest threads per slot.
Identity
ThreadRef { hw_id: u8, idx: u16 } — 4-byte positional identity used across the boundary. Waiter lists in KernelObject::{Event,Semaphore,Mutex,Thread}, state.cs_waiters, interrupts.injected_ref, and scheduler.timed_waits all store ThreadRef (not raw hw_id). After swap_remove (Axis 4 migration), refs are fixed up via MigrationFixup::apply.
Compat accessors (how ~30 call-sites survived the data-model refactor)
scheduler.ctx(hw_id) / ctx_mut(hw_id) / ctx_mut_ref(r) / state(hw_id) / tid(hw_id) / thread_handle(hw_id) / suspend_count_mut(hw_id) / current_hw_id() — each resolves through slots[hw_id].running_idx. Safe sentinel (idle_ctx) returned when running_idx is None. This let the refactor avoid rewriting every hw_threads[i].ctx site in main.rs and exports.rs.
Scheduling
HwSlot::pick_runnable— highest-priority Ready/ServicingIrq thread; tiebreak lowest idx.Scheduler::round_schedule— emits slot ids in rotating order starting fromrotation_cursor, filtered bynon_empty_runnable: u8bitset. Empty-slot fast path.OrderMode::Seededlayers Fisher-Yates on top of the filtered list.Scheduler::begin_slot_visit(hw_id)— called by main.rs at top of each slot iteration; picks runnable, setsrunning_idx, writesself.current: Option<ThreadRef>.Scheduler::decrement_quantum()— Axis 3 per-instruction tick; on hit-zero, reloads toQUANTUM_DEFAULT = 50_000and rotates within same-priority tier (observed next round, not mid-instruction).
Affinity + priority (Axis 4/5 wire-up)
KeSetAffinityThread(handle, mask) -> old_maskdoes real migration:set_affinity_reffinds the thread, updates mask, if current slot no longer allowed →swap_removefrom source slot, push onto least-depth allowed slot, rewritePCR+0x2C, returnMigrationFixup.KernelState::set_affinitywalks every waiter list and applies the fixup.- Self-migration handling: if the migrating thread is
scheduler.current, the ref is updated in place.call_export's post-call ctx restore re-readscurrent(not the stashed entry ref) so ctx lands on the new slot.main.rs's post-exportpc = lradvance usespost_ref = scheduler.currentfor the same reason. KeSetBasePriorityThread/KeQueryBasePriorityThreadstore/readGuestThread.priority: i32. NT-style [-15..+15], default 0. Drivespick_runnable.KeSetIdealProcessor/KeQueryIdealProcessor/NtSetInformationThread(classes 2/3/13) wired; ideal is a spawn-placement hint (not migrate-on-change).
Lifecycle details
exit_currentflips state toExited(code)but does NOTVec::remove(would invalidate peer ThreadRefs). Pruning happens atspawntime viaprune_exited_if_neededwhen a slot reachesPRUNE_DEPTH_THRESHOLD = 4.install_initial_threadonSchedulerlives next tospawn; both writePCR+0x2C = hw_idvia thePcrWritertrait (implGuestMemoryPcrin state.rs).KernelObject::Thread.waiters: Vec<ThreadRef>(notVec<u8>) — necessary for correctness under per-slot runqueues.
Known caveat (2026-04-23)
Axis 4's real migration distributes Sylpheed's workers across slots differently than the old 32-slot one-per-slot model. The resulting wait/signal chain trips a single scheduler.deadlock_recoveries event during boot; default force-wake recovery resolves it and the game progresses to VdSwap=2 (up from pre-Axis-4's 1). Under --halt-on-deadlock this trips scheduler.deadlock_halts = 1 at ~7.5M cycles. The issue is a latent HLE sync-primitive gap exposed by correct migration, not an Axis 4 defect. Root cause: one of tid=1/3/4/7's blocking events isn't being signaled by its expected source after thread layout changes. Track down by instrumenting the specific handle values (0x10FC, 0x1014, 0x1104, 0x10DC/0x10F0) in a future session.
Files
- xenia-cpu/src/scheduler.rs — workhorse (~35 tests covering all 5 axes)
- xenia-kernel/src/state.rs —
KernelState::set_affinityorchestrator,call_exportctx swap viaThreadRef - xenia-kernel/src/exports.rs —
ke_set_affinity_thread(0x97),ke_set_base_priority_thread(0x99),ke_query_base_priority_thread(0x81),ke_set_ideal_processor(0x98),ke_query_ideal_processor(0x82),nt_set_information_thread(0xFB) - xenia-kernel/src/objects.rs — waiter lists as
Vec<ThreadRef> - xenia-kernel/src/interrupts.rs —
injected_ref: Option<ThreadRef>(notinjected_hw: u8)
Metrics added
scheduler.spawn.ok— successful spawnsscheduler.spawn.rejected— spawn failures (should stay 0)scheduler.deadlock_recoveries— force-wake events (non-zero post-Axis-4; see caveat)scheduler.deadlock_halts— halts under--halt-on-deadlock