fix: x2APIC ICR format + build system durability docs

- Fix LocalX2Apic handler: use local_apic.x2 to select correct ICR
  format (<<32 for x2APIC, <<56 for xAPIC) instead of hardcoded <<32
- Promote x2APIC/xAPIC detection from debug! to info! for bootlog
- Document build system durability in AGENTS.md: cardinal rule,
  two-layer architecture, correct workflow, anti-patterns
This commit is contained in:
2026-05-17 13:57:37 +03:00
parent cee25393d8
commit 081ed10a8b
9 changed files with 643 additions and 6 deletions
@@ -0,0 +1,87 @@
--- src/context/switch.rs
+++ src/context/switch.rs
@@ -361,6 +361,7 @@
}
/// This is the scheduler function which currently utilises Deficit Weighted Round Robin Scheduler
+/// with NUMA-aware context selection preference.
fn select_next_context(
token: &mut CleanLockToken,
percpu: &PercpuBlock,
@@ -386,6 +387,10 @@
let total_contexts: usize = contexts_list.iter().map(|q| q.len()).sum();
let mut skipped_contexts = 0;
+ // NUMA-aware selection: remember cross-node fallback candidate.
+ let my_numa_node = percpu.numa_node.get();
+ let mut cross_node_fallback: Option<(usize, ArcContextLockWriteGuard)> = None;
+
'priority: loop {
i = (i + 1) % 40;
total_iters += 1;
@@ -450,11 +455,44 @@
// Is this context runnable on this CPU?
let sw = unsafe { update_runnable(&mut next_context_guard, cpu_id, switch_time) };
if let UpdateResult::CanSwitch = sw {
- // Cache the new context's priority for MCS lock priority donation.
- percpu.current_prio.set(next_context_guard.prio);
- next_context_guard_opt = Some(next_context_guard);
- balance[i] -= SCHED_PRIO_TO_WEIGHT[20];
- break 'priority;
+ // NUMA-aware selection: check if this context's last CPU was on the same node.
+ let same_node = if my_numa_node != u8::MAX {
+ next_context_guard.cpu_id
+ .map(|cid| {
+ crate::percpu::get_for_cpu(cid)
+ .map(|p| p.numa_node.get() == my_numa_node)
+ .unwrap_or(false)
+ })
+ .unwrap_or(true) // New context (no last CPU) — treat as same node
+ } else {
+ true // No NUMA info — treat all as same node
+ };
+
+ if same_node {
+ // Cache-warm: select immediately
+ percpu.current_prio.set(next_context_guard.prio);
+ next_context_guard_opt = Some(next_context_guard);
+ balance[i] -= SCHED_PRIO_TO_WEIGHT[20];
+ break 'priority;
+ } else {
+ // Cross-node candidate: save as fallback, keep scanning for same-node
+ if cross_node_fallback.is_none() {
+ // Cache the priority and balance for later
+ cross_node_fallback =
+ Some((next_context_guard.prio, next_context_guard));
+ balance[i] -= SCHED_PRIO_TO_WEIGHT[20];
+ // Don't break — keep looking for a same-node context
+ continue;
+ } else {
+ // Already have a cross-node fallback; push this one back
+ contexts.push_back(next_context_ref);
+ skipped_contexts += 1;
+ if skipped_contexts >= total_contexts {
+ break 'priority;
+ }
+ continue;
+ }
+ }
} else {
if matches!(sw, UpdateResult::Blocked) {
idle_contexts(token.token()).push_back(next_context_ref);
@@ -469,6 +507,15 @@
}
}
}
+
+ // If we found a cross-node fallback but no same-node context, use it
+ if next_context_guard_opt.is_none() {
+ if let Some((prio, guard)) = cross_node_fallback {
+ percpu.current_prio.set(prio);
+ next_context_guard_opt = Some(guard);
+ }
+ }
+
percpu.balance.set(balance);
percpu.last_queue.set(i);