Skip to main content

scx_pandemonium/
topology.rs

1// PANDEMONIUM CPU CACHE TOPOLOGY
2// PARSES SYSFS AT STARTUP, POPULATES BPF MAP FOR CACHE-AWARE DISPATCH
3//
4// BPF dispatch() USES THE CACHE DOMAIN MAP TO PREFER TASKS THAT LAST
5// RAN ON THE SAME CPU OR AN L2 SIBLING. THIS PRESERVES CACHE WARMTH
6// AND REDUCES THE THROUGHPUT GAP CAUSED BY BLIND NODE-DSQ CONSUMPTION.
7
8use anyhow::Result;
9
10use crate::scheduler::Scheduler;
11
12// FIEDLER-VALUE / TOPOLOGY TIME CONSTANT
13// lambda_2 IS THE SECOND-SMALLEST EIGENVALUE OF THE WEIGHTED GRAPH LAPLACIAN
14// (THE "ALGEBRAIC CONNECTIVITY" OR "SPECTRAL GAP"). 1/lambda_2 IS THE MIXING
15// TIME OF A RANDOM WALK ACROSS THE CPU GRAPH -- A CANONICAL "TIME CONSTANT"
16// FOR HOW FAST WORK PROPAGATES ACROSS THE TOPOLOGY. EVERY TIMING/THRESHOLD
17// FORMULA IN THE SCHEDULER DERIVES FROM tau VIA scale_tau() (BPF) OR
18// scale_tau_u64() (RUST); ad-hoc nr_cpus FORMULAS ARE CRUDE APPROXIMATIONS
19// OF THIS AND ARE EXPLICITLY MIGRATED OUT.
20//
21// CARVE-OUT: ONLY ABSOLUTE-COUNT QUANTITIES KEEP nr_cpu_ids. GRAPH-SHAPE
22// QUANTITIES, SEARCH BUDGETS INCLUDED, GO THROUGH tau VIA
23// lambda_2 = TAU_SCALE_NS / tau. TWO SITES IN main.bpf.c KEEP nr_cpu_ids:
24//   select_cpu()'s wake_wide THRESHOLD, WHICH MATCHES A KERNEL CONVENTION
25//   tick()'s ROTATING-SCAN BUDGET, WHICH IS COVERAGE, NOT GRAPH SHAPE
26//
27// EXTRACTION IS O(n log n) ON TOP OF THE EXISTING O(n^3) JACOBI.
28// REFERENCE: CHEEGER'S INEQUALITY BOUNDS lambda_2 AGAINST GRAPH BOTTLENECK.
29const LAMBDA_ZERO_EPS: f64 = 1e-8;
30const TAU_SCALE_NS: f64 = 1.6e8; // 160MS. CAPACITY-AWARE ANCHOR: AT THE
31// 12C REFERENCE (lambda_2=12, N=12)
32// tau = 160ms / sqrt(144) = 13.3MS.
33const TAU_FLOOR_NS: u64 = 1_000_000; //  1MS
34const TAU_CEIL_NS: u64 = 40_000_000; // 40MS
35
36// THE OSCILLATOR'S WORKING BAND, MIRRORING K_CODEL_FLOOR AND K_CODEL_MAX IN
37// main.bpf.c. c_eq IS A POSITION INSIDE THIS BAND, SO IT NEEDS NO CLAMP OF ITS
38// OWN -- SEE compute_codel_eq_ns.
39const K_CODEL_FLOOR_Q16: f64 = 1147.0; // 0.0175 * tau
40const K_CODEL_MAX_Q16: f64 = 3277.0; // 0.05   * tau
41const Q16: f64 = 65536.0;
42
43#[derive(Clone, Copy, Debug)]
44pub struct TopologySpectrum {
45    pub fiedler: f64,     // lambda_2
46    pub tau_ns: u64,      // clamped TAU_SCALE_NS / sqrt(fiedler * N)
47    pub codel_eq_ns: u64, // A POSITION IN [K_CODEL_FLOOR, K_CODEL_MAX]*tau
48    // SET BY THE SPECTRAL-GAP DEFICIT. NO CLAMP.
49    // Phi migration-potential distance->wait scale (Q16). The extra head-wait a
50    // steal must clear before crossing to a peer = (reff * this) >> 16 ns. Always
51    // computed now (T1): the continuous metric prices distance on every part, no
52    // binary topology gate -- on a monolithic part it calibrates to the L2 seam.
53    pub phi_dist_scale_q16: u64,
54}
55
56// T2: the emergent domain tree. Leaves are tightly-coupled CPU sets (an L2 group
57// / core -- nothing meaningful to partition below); internal nodes are a
58// min-conductance cut carrying its phi, the price to cross that seam. T3's
59// bounded-local steal climbs this tree: drain your leaf, then your subtree,
60// crossing a cut only when its phi says the imbalance pays. The discrete cache domain
61// enum is gone -- this structure emerges from the cache graph, per machine.
62#[derive(Debug, Clone)]
63pub enum DomainNode {
64    Leaf(Vec<usize>),
65    Cut {
66        phi: f64,
67        left: Box<DomainNode>,
68        right: Box<DomainNode>,
69    },
70}
71
72impl DomainNode {
73    // Flatten to the leaf CPU sets -- each an emergent atomic domain.
74    pub fn leaves(&self) -> Vec<Vec<usize>> {
75        match self {
76            DomainNode::Leaf(cpus) => vec![cpus.clone()],
77            DomainNode::Cut { left, right, .. } => {
78                let mut v = left.leaves();
79                v.extend(right.leaves());
80                v
81            }
82        }
83    }
84
85    // Every cut's phi -- the crossing-price ladder (coarser seams cost less, so
86    // phi rises with depth as the steal climbs toward more tightly-coupled work).
87    pub fn cut_phis(&self) -> Vec<f64> {
88        match self {
89            DomainNode::Leaf(_) => Vec::new(),
90            DomainNode::Cut { phi, left, right } => {
91                let mut v = vec![*phi];
92                v.extend(left.cut_phis());
93                v.extend(right.cut_phis());
94                v
95            }
96        }
97    }
98}
99
100fn extract_fiedler(eigenvalues: &[f64]) -> f64 {
101    // Jacobi RETURNS EIGENVALUES UNSORTED. FOR A CONNECTED LAPLACIAN THE
102    // SMALLEST EIGENVALUE IS 0 (SKIPPED VIA LAMBDA_ZERO_EPS). FOR A
103    // DISCONNECTED GRAPH (HOTPLUG PARTITION) SEVERAL EIGENVALUES ARE ~0;
104    // lambda_2 IS THE SMALLEST STRICTLY POSITIVE ONE.
105    let mut v: Vec<f64> = eigenvalues.to_vec();
106    v.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
107    v.into_iter()
108        .find(|&x| x > LAMBDA_ZERO_EPS)
109        .unwrap_or(LAMBDA_ZERO_EPS)
110}
111
112fn compute_tau_ns(fiedler: f64, n: usize) -> u64 {
113    // CAPACITY-AWARE: tau = TAU_SCALE_NS / sqrt(lambda_2 * N) -- the geometric
114    // mean of connectivity (1/lambda_2) and capacity (1/sqrt(N)). The old
115    // pure-connectivity law gave a well-connected but capacity-starved
116    // topology (a 2-core L2 pair, lambda_2=20) a tiny tau (8ms) and thus tight
117    // tolerances exactly where scarce CPUs need loose ones -- which the
118    // apply_tau_scaling floors then patched back up. sqrt(N) penalizes small N,
119    // so 2C loosens to ~25ms with no floor needed. The 12C reference is
120    // preserved: lambda_2=12, N=12 -> sqrt(144)=12 -> 160ms/12 = 13.3ms.
121    let denom = (fiedler.max(LAMBDA_ZERO_EPS) * (n.max(1) as f64)).sqrt();
122    let raw = TAU_SCALE_NS / denom.max(LAMBDA_ZERO_EPS);
123    (raw as u64).clamp(TAU_FLOOR_NS, TAU_CEIL_NS)
124}
125
126// CoDel TARGET EQUILIBRIUM FROM THE LAPLACIAN SPECTRUM.
127//   s    = 1 - lambda_2 * Tr(L+) / (N - 1)
128//   c_eq = (K_CODEL_FLOOR + (K_CODEL_MAX - K_CODEL_FLOOR) * s) * tau
129//
130// s IS THE NORMALISED SPECTRAL-GAP DEFICIT. lambda_2 * Tr(L+) / (N-1) is
131// lambda_2 over the HARMONIC MEAN of the positive eigenvalues: 1 on a flat
132// spectrum, where no cut is worse than any other, falling toward 1/(N-1) when
133// lambda_2 sits far below every other cut. So s measures HOW MUCH WORSE THE
134// BOTTLENECK IS THAN THE GRAPH'S TYPICAL CONNECTIVITY, which is the quantity
135// "how long should work be tolerated not moving" keys on. Cheeger already ties
136// lambda_2 to that bottleneck, so this reads the same object the tau law uses.
137//
138// IT IS A POSITION IN THE BAND, NOT A QUANTITY CLAMPED INTO ONE. Tr(L+) >=
139// 1/lambda_2 bounds s at 0 and Tr(L+) <= (N-1)/lambda_2 bounds it at
140// 1 - 1/(N-1), so c_eq lands inside [K_CODEL_FLOOR*tau, K_CODEL_MAX*tau] by
141// construction and main.bpf.c's re-clamp into that same window never fires.
142// THAT IS THE POINT: an equilibrium equal to the band's ceiling gives the
143// oscillator a spring that can only push one way.
144//
145// THE ENDS. A 2-CPU graph has exactly one positive eigenvalue, so s is 0 and
146// the target sits at the floor -- two CPUs have no far tier and stalls should
147// be caught as hard as the band allows. An SMT part, where a stiff sibling edge
148// sits beside a weak cross-domain cut, reads s ~0.72 and tolerates longer.
149fn compute_codel_eq_ns(eigenvalues: &[f64], n: usize, tau_ns: u64) -> u64 {
150    let floor_ns = (K_CODEL_FLOOR_Q16 / Q16) * tau_ns as f64;
151    if n < 2 {
152        return floor_ns as u64;
153    }
154    let mut sum_inv_lambda = 0.0f64;
155    let mut n_pos = 0usize;
156    let mut lambda_2 = f64::MAX;
157    for &lambda in eigenvalues {
158        if lambda > LAMBDA_ZERO_EPS {
159            sum_inv_lambda += 1.0 / lambda;
160            n_pos += 1;
161            if lambda < lambda_2 {
162                lambda_2 = lambda;
163            }
164        }
165    }
166    // A DISCONNECTED OR DEGENERATE GRAPH HAS NO GAP TO READ. Fall to the floor,
167    // which is the tightest the band allows and the safe direction for a
168    // topology this cannot describe.
169    if n_pos == 0 || lambda_2 == f64::MAX || sum_inv_lambda <= 0.0 {
170        return floor_ns as u64;
171    }
172    let s = (1.0 - lambda_2 * sum_inv_lambda / (n - 1) as f64).clamp(0.0, 1.0);
173    let span_ns = ((K_CODEL_MAX_Q16 - K_CODEL_FLOOR_Q16) / Q16) * tau_ns as f64;
174    (floor_ns + span_ns * s) as u64
175}
176
177pub struct CpuTopology {
178    pub nr_cpus: usize,
179    pub l2_domain: Vec<u32>,      // l2_domain[cpu] = group_id
180    pub l2_groups: Vec<Vec<u32>>, // l2_groups[group_id] = [cpu, ...]
181    pub socket_domain: Vec<u32>,  // socket_domain[cpu] = socket_id
182    pub llc_domain: Vec<u32>,     // llc_domain[cpu] = L3 GROUP (== socket WHEN MONOLITHIC)
183    pub nr_sockets: u32,
184}
185
186impl CpuTopology {
187    pub fn detect(nr_cpus: usize) -> Result<Self> {
188        // TWO-PASS L2 DETECTION: A SINGLE PASS HAS TWO DEFECT CLASSES ON THE
189        // SYSFS-FAILURE PATH (THE NORMAL CASE FOR OFFLINE CPUs AND UNDER
190        // QEMU/KVM):
191        //   1. CPU-0 ALIASING: A FAILED READ THAT ASSIGNS l2_domain[cpu] = cpu
192        //      AS A SYNTHETIC ID NEVER REGISTERED IN l2_groups CAN COLLIDE
193        //      WITH A REAL GROUP ID; populate_l2_siblings_map NEVER WRITES
194        //      THAT SLOT, SO THE BPF SIDE READS THE MAP'S ZERO-INIT AND
195        //      find_idle_l2_sibling SILENTLY TREATS CPU 0 AS EVERYONE'S L2
196        //      SIBLING.
197        //   2. ORDER DEPENDENCE: IF cpu A's READ TRANSIENTLY FAILS BUT cpu B's
198        //      SUCCEEDS AND LISTS A AS A GENUINE SIBLING ("A,B"), THE TWO ARE
199        //      NEVER UNIFIED -- AN ARTIFACT OF PROCESSING ORDER ALONE.
200        // PASS 1 BUILDS THE REAL GROUPS FROM EVERY SUCCESSFUL READ; PASS 2
201        // PLACES THE FAILURES -- JOINING A GROUP THAT ALREADY LISTS THEM, ELSE
202        // A REGISTERED SINGLETON GROUP WITH A COLLISION-FREE SEQUENTIAL ID.
203        let mut l2_domain = vec![u32::MAX; nr_cpus];
204        let mut seen_groups: Vec<Vec<u32>> = Vec::new();
205
206        for cpu in 0..nr_cpus {
207            let path = format!(
208                "/sys/devices/system/cpu/cpu{}/cache/index2/shared_cpu_list",
209                cpu
210            );
211            let content = match std::fs::read_to_string(&path) {
212                Ok(s) => s,
213                Err(_) => continue, // placed in pass 2
214            };
215
216            let members = parse_cpu_list(content.trim());
217
218            // CHECK IF THIS GROUP ALREADY EXISTS
219            let group_id = match seen_groups.iter().position(|g| *g == members) {
220                Some(id) => id as u32,
221                None => {
222                    let id = seen_groups.len() as u32;
223                    seen_groups.push(members.clone());
224                    id
225                }
226            };
227
228            l2_domain[cpu] = group_id;
229        }
230        for cpu in 0..nr_cpus {
231            if l2_domain[cpu] != u32::MAX {
232                continue;
233            }
234            if let Some(id) = seen_groups.iter().position(|g| g.contains(&(cpu as u32))) {
235                l2_domain[cpu] = id as u32;
236            } else {
237                let id = seen_groups.len() as u32;
238                seen_groups.push(vec![cpu as u32]);
239                l2_domain[cpu] = id;
240            }
241        }
242
243        // DETECT SOCKET (PHYSICAL PACKAGE)
244        let mut socket_domain = vec![0u32; nr_cpus];
245        let mut seen_sockets: Vec<u32> = Vec::new();
246
247        for cpu in 0..nr_cpus {
248            let path = format!(
249                "/sys/devices/system/cpu/cpu{}/topology/physical_package_id",
250                cpu
251            );
252            let pkg_id = match std::fs::read_to_string(&path) {
253                Ok(s) => s.trim().parse::<u32>().unwrap_or(0),
254                Err(_) => 0,
255            };
256            if !seen_sockets.contains(&pkg_id) {
257                seen_sockets.push(pkg_id);
258            }
259            let socket_idx = seen_sockets.iter().position(|&s| s == pkg_id).unwrap() as u32;
260            socket_domain[cpu] = socket_idx;
261        }
262
263        let nr_sockets = seen_sockets.len() as u32;
264
265        // DETECT L3 / cache domain / cache domain DOMAIN (index3). ON AMD multi-domain PARTS index3
266        // SUBDIVIDES THE SOCKET INTO cache domain GROUPS; ON MONOLITHIC-L3 PARTS IT
267        // SPANS THE WHOLE SOCKET. USE THE TIER ONLY WHEN IT GENUINELY
268        // SUBDIVIDES A SOCKET (MORE L3 GROUPS THAN SOCKETS) AND index3 WAS
269        // PRESENT FOR EVERY CPU; OTHERWISE llc_domain == socket_domain SO THE
270        // CROSS-DOMAIN RUNG IN build_laplacian NEVER FIRES (EXACT NO-OP).
271        let mut llc_domain = vec![0u32; nr_cpus];
272        let mut seen_llc: Vec<Vec<u32>> = Vec::new();
273        let mut llc_ok = true;
274        for cpu in 0..nr_cpus {
275            let path = format!(
276                "/sys/devices/system/cpu/cpu{}/cache/index3/shared_cpu_list",
277                cpu
278            );
279            let content = match std::fs::read_to_string(&path) {
280                Ok(s) => s,
281                Err(_) => {
282                    llc_ok = false;
283                    break;
284                }
285            };
286            let members = parse_cpu_list(content.trim());
287            let group_id = match seen_llc.iter().position(|g| *g == members) {
288                Some(id) => id as u32,
289                None => {
290                    let id = seen_llc.len() as u32;
291                    seen_llc.push(members);
292                    id
293                }
294            };
295            llc_domain[cpu] = group_id;
296        }
297        // MAX_OVERFLOW_DOMAINS mirrors src/bpf/intf.h: the BPF side creates exactly
298        // this many per-domain overflow DSQs. If a (multi-socket, high-cache domain) box has
299        // more L3 groups than that, degrade to the socket domain rather than let
300        // a cache domain id index an uncreated DSQ -> dispatch failure -> ejection.
301        const MAX_OVERFLOW_DOMAINS: usize = 32;
302        let llc_subdivides = llc_ok
303            && seen_llc.len() > nr_sockets as usize
304            && seen_llc.len() <= MAX_OVERFLOW_DOMAINS;
305        if !llc_subdivides {
306            llc_domain = socket_domain.clone();
307        }
308
309        Ok(Self {
310            nr_cpus,
311            l2_domain,
312            l2_groups: seen_groups,
313            socket_domain,
314            llc_domain,
315            nr_sockets,
316        })
317    }
318
319    // ONLINE CPU COUNT, UNPRIVILEGED READ. THE HOTPLUG POLL'S CHANGE SIGNAL.
320    pub fn online_cpu_count() -> usize {
321        std::fs::read_to_string("/sys/devices/system/cpu/online")
322            .map(|s| parse_cpu_list(s.trim()).len())
323            .unwrap_or(0)
324    }
325
326    // DETECT-AND-POPULATE, THE ONE STARTUP/HOTPLUG SEQUENCE. ALL RUST-SIDE
327    // COMPUTATION RUNS BEFORE ANY OF THE MAP WRITES: THERE IS NO CROSS-MAP
328    // TRANSACTION PRIMITIVE IN eBPF, SO THE EXPOSURE WHILE MAPS DISAGREE IS
329    // BOUNDED BY WRITE TIME ALONE -- INTERLEAVING THE O(n^3)
330    // EIGENDECOMPOSITION OR THE DOMAIN-TREE CUTS BETWEEN WRITES WOULD STRETCH
331    // THAT WINDOW FOR NOTHING. WRITE ORDER:
332    // cache_domain + l2_siblings (USED TOGETHER), THE AFFINITY-RANK FAMILY,
333    // cpu_domain, AND THE TUNING KNOBS LAST -- THE "GO" SIGNAL THAT TRIGGERS
334    // BPF'S OWN tau-SCALED RE-DERIVATION.
335    pub fn detect_and_populate(sched: &mut Scheduler, nr_cpus: usize) -> Result<TopologySpectrum> {
336        let topo = Self::detect(nr_cpus)?;
337        topo.log_summary();
338        let (reff, rank, spectrum) = topo.compute_resistance_affinity();
339        topo.log_resistance_affinity(&reff, &rank, spectrum);
340        let domains = topo.compute_domain_tree();
341        topo.log_domains(&domains);
342        let domain_phi = topo.domain_cross_phi_matrix(&domains);
343        let ov_domains = topo.overflow_domain_count();
344        let cpu_dom = topo.domain_partition(&domains, ov_domains);
345
346        if let Err(e) = topo.populate_bpf_map(sched) {
347            log_warn!("CACHE TOPOLOGY MAP WRITE FAILED: {}", e);
348        }
349        if let Err(e) = topo.populate_l2_siblings_map(sched) {
350            log_warn!("L2 SIBLINGS MAP WRITE FAILED: {}", e);
351        }
352        if let Err(e) = topo.populate_affinity_rank_map(
353            sched,
354            &reff,
355            &rank,
356            spectrum.phi_dist_scale_q16,
357            spectrum.codel_eq_ns,
358            &domain_phi,
359        ) {
360            log_warn!("AFFINITY RANK MAP WRITE FAILED: {}", e);
361        }
362        for (cpu, &d) in cpu_dom.iter().enumerate() {
363            if let Err(e) = sched.write_cpu_domain(cpu as u32, d) {
364                log_warn!("CPU DOMAIN MAP WRITE FAILED (cpu {}): {}", cpu, e);
365                break;
366            }
367        }
368        log_info!(
369            "OVERFLOW DOMAINS: {} (emergent, cpu_domain populated)",
370            ov_domains
371        );
372        let dom_peers = topo.min_in_domain_peers(&rank, &cpu_dom);
373        sched.write_affinity_domain_peers(dom_peers);
374        log_info!(
375            "AFFINITY DOMAIN PEERS: {} (min over CPUs; bounds the \
376                   idle-search floor)",
377            dom_peers
378        );
379        if let Err(e) = sched.write_topology_fields(spectrum.tau_ns, spectrum.codel_eq_ns) {
380            log_warn!("TOPOLOGY KNOB WRITE FAILED: {}", e);
381        }
382        Ok(spectrum)
383    }
384
385    // HOTPLUG POLL, THE ONE IMPLEMENTATION: compare the online-CPU count
386    // against the last observed value; on change, log and re-run the full
387    // detect-and-populate so a CPU broken at boot (the L2 singleton fallback)
388    // self-corrects once real sysfs data exists, and every R_eff/phi/domain
389    // table tracks the live width. Called each tick by BOTH control loops.
390    // Returns true when it fired -- the adaptive loop keys its tau refresh on it.
391    pub fn poll_hotplug(sched: &mut Scheduler, nr_cpus: usize, last_online: &mut usize) -> bool {
392        let now = Self::online_cpu_count();
393        if now == 0 || now == *last_online {
394            return false;
395        }
396        log_info!(
397            "HOTPLUG: online CPUs {} -> {} -- re-deriving topology",
398            *last_online,
399            now
400        );
401        *last_online = now;
402        if let Err(e) = Self::detect_and_populate(sched, nr_cpus) {
403            log_warn!("HOTPLUG TOPOLOGY RE-DETECT FAILED: {}", e);
404        }
405        true
406    }
407
408    // WRITE L2 DOMAIN MAP TO BPF ARRAY VIA SCHEDULER
409    pub fn populate_bpf_map(&self, sched: &mut Scheduler) -> Result<()> {
410        for cpu in 0..self.nr_cpus {
411            sched.write_cache_domain(cpu as u32, self.l2_domain[cpu])?;
412        }
413        // nr_overflow_domains = number of distinct llc_domain values (the overflow-domain count)
414        let mut seen: Vec<u32> = Vec::new();
415        for &g in &self.llc_domain {
416            if !seen.contains(&g) {
417                seen.push(g);
418            }
419        }
420        sched.write_nr_overflow_domains(seen.len() as u32);
421        Ok(())
422    }
423
424    // WRITE L2 SIBLINGS FLAT ARRAY TO BPF MAP
425    // l2_siblings[group_id * 8 + slot] = cpu_id, SENTINEL u32::MAX MARKS END
426    pub fn populate_l2_siblings_map(&self, sched: &Scheduler) -> Result<()> {
427        const MAX_L2_SIBLINGS: usize = 8;
428        for (gid, members) in self.l2_groups.iter().enumerate() {
429            for (slot, &cpu) in members.iter().enumerate().take(MAX_L2_SIBLINGS) {
430                sched.write_l2_sibling(gid as u32, slot as u32, cpu)?;
431            }
432            if members.len() < MAX_L2_SIBLINGS {
433                sched.write_l2_sibling(gid as u32, members.len() as u32, u32::MAX)?;
434            }
435        }
436        Ok(())
437    }
438
439    // RESISTANCE AFFINITY. EFFECTIVE RESISTANCE R_eff(u,v) IS THE MIGRATION COST
440    // BETWEEN TWO CPUs THROUGH ALL TOPOLOGY PATHS, FROM THE LAPLACIAN
441    // PSEUDOINVERSE OF THE CPU GRAPH:
442    //   L  = D - W          DEGREE MINUS WEIGHTED ADJACENCY
443    //   L+ = sum_{lambda_i > 0} (1/lambda_i) * v_i * v_i^T
444    //   R_eff(i,j) = L+[i,i] + L+[j,j] - 2*L+[i,j]
445    //
446    // EDGE CONDUCTANCES, THE INVERSE OF RESISTANCE:
447    //   L2 SIBLINGS          10.0   SHARED L2, NEAR-ZERO MIGRATION COST
448    //   SAME L3              3.0    SHARED LLC
449    //   CROSS-DOMAIN SOCKET  1.0    INTERCONNECT HOP, ~8x CORE-TO-CORE LATENCY
450    //   CROSS-SOCKET         0.3    NUMA HOP
451    // THE SAME-DOMAIN RUNG IS RAISED RATHER THAN THE CROSS-DOMAIN CUT LOWERED, SO
452    // SAME-L3 PEERS RANK AHEAD OF CROSS-L3 ONES WITHOUT MOVING lambda_2 -- THE
453    // CROSS-L3 CUT STAYS 1.0 AND tau AND codel_eq ARE UNCHANGED. ON A MONOLITHIC
454    // PART THE L3 RUNG COINCIDES WITH THE SOCKET RUNG.
455    //
456    // O(n^3) FOR n CPUs, WHICH IS TRIVIAL AT STARTUP FOR n <= 256.
457    // REFERENCE: Christiano-Kelner-Madry-Spielman-Teng (STOC 2011),
458    //            Chen-Kyng-Liu-Peng-Gutenberg-Sachdeva (FOCS 2022)
459
460    // STIFF L2 RUNG: SMT siblings SHARE L2, so a move between them costs ~0 cache.
461    // Make the edge very stiff -> R_eff(SMT-sib) ~ 0, which lands the Phi migration
462    // barrier exactly at the physical-core / L2 boundary (a real cold-L2 refill)
463    // instead of penalizing free intra-core moves. lambda_2 is the cross-domain Fiedler
464    // cut (independent of L2 stiffness) so tau is unchanged, and codel_eq is already
465    // clamped at its ceiling, so the oscillator timescales are invariant.
466    const CONDUCTANCE_L2: f64 = 1000.0; // L2 / SMT SIBLINGS
467    const CONDUCTANCE_LLC: f64 = 3.0; // SAME L3 (ABOVE SOCKET; always-on rung)
468    const CONDUCTANCE_SOCKET: f64 = 1.0; // SAME SOCKET, CROSS-DOMAIN (IF HOP) -- OR MONOLITHIC SAME-SOCKET
469    const CONDUCTANCE_CROSS: f64 = 0.3; // CROSS-SOCKET NUMA HOP
470
471    // INTEGER MIRROR OF THE RUNGS ABOVE, x10 SO THE 0.3 CROSS-SOCKET CUT CLEARS
472    // TO AN INTEGER (SPILL-Phi). SAME RATIOS AS THE f64 TABLE --
473    // INCLUDING THE STIFF L2 RUNG -- SO THE EXACT AND FLOAT PATHS PRICE THE
474    // SAME GRAPH. THE STIFF L2 RUNG (10000) GROWS BAREISS INTERMEDIATES FAST:
475    // i128 HOLDS THE DETERMINANTS TO ROUGHLY 8 CPUs ON AN SMT PART AND THE
476    // checked_mul GUARD FALLS BACK TO THE FLOAT PSEUDOINVERSE ABOVE THAT --
477    // EXACT WHERE INTEGERS HOLD, FLOAT WHERE THEY DON'T, BY CONSTRUCTION.
478    const ICOND_L2: i128 = 10000;
479    const ICOND_LLC: i128 = 30;
480    const ICOND_SOCKET: i128 = 10;
481    const ICOND_CROSS: i128 = 3;
482    const ICOND_RECOVER: f64 = 10.0; // undo the x10 conductance scale
483    const REFF_EXACT_MAX_CPUS: usize = 24;
484
485    // SPILL-Phi PLACEMENT DEPTH THRESHOLDS. RUST FOLDS R_eff INTO A PER-PEER
486    // DSQ-DEPTH CAP THE BPF SPILL HELPER APPLIES: NEAR PEERS (R_eff~0) ACCEPT
487    // SPILLS UP TO SPILL_NEAR_DEPTH, THE MOST DISTANT ONLY WHEN NEAR-EMPTY
488    // (SPILL_FAR_DEPTH). MONOLITHIC (NO DISTANCE STRUCTURE TO PRICE) -> FLAT
489    // SPILL_MONO_DEPTH.
490    // FAR_DEPTH=1 IS THE SHIPPED SETTING: THE STRICT FORM -- A FAR PEER TAKES
491    // A SPILL ONLY WHEN EMPTY. MEASURED ON IT: LOCALITY 77.9% CACHE-LOCAL AT
492    // 86k MIGRATIONS/s, LONGRUN p99 0.58x VS THE ARCHIVED PEAK, ipc NEUTRAL;
493    // AN UNPROVEN BURST/MIXED DRIFT (~1.2x, p >= 0.67 AT N=2) IS TRACKED,
494    // WITH A FAR_DEPTH=2 A/B QUEUED IN ROADMAP.md. CHANGE THIS VALUE ONLY
495    // WITH THAT A/B IN HAND.
496
497    // BUILD WEIGHTED GRAPH LAPLACIAN FROM CPU TOPOLOGY
498    // Conductance edge weight between two CPUs, derived from the cache hierarchy.
499    // The single source of truth for BOTH the Laplacian (R_eff / tau) and the
500    // domain cut below, so the emergent locality boundary and the placement metric
501    // price the exact same graph -- the continuous metric drives everything.
502    fn conductance(&self, i: usize, j: usize) -> f64 {
503        if self.l2_domain[i] == self.l2_domain[j] {
504            Self::CONDUCTANCE_L2
505        } else if self.llc_domain[i] == self.llc_domain[j] {
506            Self::CONDUCTANCE_LLC
507        } else if self.socket_domain[i] == self.socket_domain[j] {
508            Self::CONDUCTANCE_SOCKET
509        } else {
510            Self::CONDUCTANCE_CROSS
511        }
512    }
513
514    fn build_laplacian(&self) -> Vec<f64> {
515        let n = self.nr_cpus;
516        let mut l = vec![0.0f64; n * n];
517        for i in 0..n {
518            for j in (i + 1)..n {
519                let w = self.conductance(i, j);
520                l[i * n + j] = -w;
521                l[j * n + i] = -w;
522                l[i * n + i] += w;
523                l[j * n + j] += w;
524            }
525        }
526        l
527    }
528
529    // ---- T2: emergent domain cut (SOSA min-conductance) --------------------
530    // The discrete cache domain layer is replaced by domains that EMERGE from the
531    // cache graph. The boundary is the min-conductance cut: phi = cut_weight /
532    // min(vol_a, vol_b). Low phi = a loosely-coupled seam = a real domain edge;
533    // the phi of the cut IS the cross-domain crossing price (THE FLAG: the price
534    // draws the boundary, no gate). Balance-free -- the seam falls where the
535    // silicon divides (asymmetric X3D / P+E included), not where volume balances.
536
537    // Conductance phi of a bipartition of `members` (in_side[c] = c is on side A).
538    // O(|members|^2); boot-time only. The random-walk variant (next) avoids the
539    // full scan for large N -- this exact form is the ground-truth + the price.
540    fn cut_conductance(&self, members: &[usize], in_side: &[bool]) -> f64 {
541        let mut cut = 0.0f64;
542        let (mut vol_a, mut vol_b) = (0.0f64, 0.0f64);
543        for &a in members {
544            for &b in members {
545                if a == b {
546                    continue;
547                }
548                let w = self.conductance(a, b);
549                if in_side[a] {
550                    vol_a += w;
551                } else {
552                    vol_b += w;
553                }
554                if in_side[a] != in_side[b] {
555                    cut += w; // each crossing edge counted twice (a,b and b,a)
556                }
557            }
558        }
559        cut /= 2.0;
560        let denom = vol_a.min(vol_b);
561        if denom <= 0.0 {
562            f64::INFINITY
563        } else {
564            cut / denom
565        }
566    }
567
568    // Fiedler vector (eigenvector of lambda_2) of the full graph -- the ground
569    // truth the scalable random-walk cut is cross-checked against. eigenvectors
570    // are column-major: component i of eigenvector k is eigenvectors[i*n + k].
571    fn fiedler_vector(eigenvalues: &[f64], eigenvectors: &[f64], n: usize) -> Vec<f64> {
572        let mut idx: Vec<usize> = (0..n).collect();
573        idx.sort_by(|&a, &b| {
574            eigenvalues[a]
575                .partial_cmp(&eigenvalues[b])
576                .unwrap_or(std::cmp::Ordering::Equal)
577        });
578        let k = if n >= 2 { idx[1] } else { idx[0] }; // 2nd smallest = lambda_2
579        (0..n).map(|i| eigenvectors[i * n + k]).collect()
580    }
581
582    // True when every member shares one L2 group -- the atomic leaf. L2 siblings
583    // are maximally coupled; there is no meaningful seam to find below them.
584    fn all_same_l2(&self, members: &[usize]) -> bool {
585        members
586            .windows(2)
587            .all(|w| self.l2_domain[w[0]] == self.l2_domain[w[1]])
588    }
589
590    // Min-conductance cut of an ARBITRARY CPU subset (recursion-safe). The global
591    // Fiedler degrades inside a subtree, so this builds the INDUCED Laplacian on
592    // `members`, eigendecomposes it, and sweeps by the SUBSET's own Fiedler. (2d
593    // replaces these internals with a local random walk -- no eigensolve, so it
594    // scales; the tree-builder is agnostic to which produces the cut.) Returns
595    // global CPU ids.
596    fn domain_cut(&self, members: &[usize]) -> Option<(Vec<usize>, Vec<usize>, f64)> {
597        let k = members.len();
598        if k < 2 {
599            return None;
600        }
601        let mut lap = vec![0.0f64; k * k]; // induced Laplacian, local-indexed 0..k
602        for ia in 0..k {
603            for ib in (ia + 1)..k {
604                let w = self.conductance(members[ia], members[ib]);
605                lap[ia * k + ib] = -w;
606                lap[ib * k + ia] = -w;
607                lap[ia * k + ia] += w;
608                lap[ib * k + ib] += w;
609            }
610        }
611        let (ev, evec) = Self::symmetric_eigen(&lap, k);
612        let fsub = Self::fiedler_vector(&ev, &evec, k); // local-indexed
613        let mut order: Vec<usize> = (0..k).collect();
614        order.sort_by(|&a, &b| {
615            fsub[a]
616                .partial_cmp(&fsub[b])
617                .unwrap_or(std::cmp::Ordering::Equal)
618        });
619        let mut in_side = vec![false; self.nr_cpus]; // global-indexed for cut_conductance
620        let (mut best_phi, mut best_k) = (f64::INFINITY, 1usize);
621        for s in 1..k {
622            in_side[members[order[s - 1]]] = true; // grow the prefix (global id)
623            let phi = self.cut_conductance(members, &in_side);
624            if phi < best_phi {
625                best_phi = phi;
626                best_k = s;
627            }
628        }
629        let side_a: Vec<usize> = order[..best_k].iter().map(|&li| members[li]).collect();
630        let side_b: Vec<usize> = order[best_k..].iter().map(|&li| members[li]).collect();
631        Some((side_a, side_b, best_phi))
632    }
633
634    // Below this many CPUs the exact eigen cut is cheap, so use it; above it,
635    // the O(n^3) Jacobi is the wall and the random-walk cut takes over. This is an
636    // implementation dispatch on cost, not a placement gate (THE FLAG untouched).
637    const WALK_CUT_THRESHOLD: usize = 64;
638
639    // SCALABLE min-conductance cut: the SAME sweep as domain_cut, but the vertex
640    // ordering comes from a RANDOM WALK instead of an eigendecomposition -- no
641    // O(n^3) eigensolve, so it scales. Power iteration on the NORMALIZED operator
642    // M = 2I - L_sym, where L_sym = I - D^{-1/2} A D^{-1/2} is the symmetric
643    // normalized Laplacian (eigenvalues in [0,2] regardless of edge-weight scale,
644    // so stiff L2 edges don't dominate the shift the way a raw cI - L would). Its
645    // null eigenvector is D^{1/2}*1 (the walk's stationary distribution, deflated
646    // out each step); the next is the normalized Fiedler -- the iterate converges
647    // to it with a gap set by the CONDUCTANCE structure, not the weight magnitude.
648    // The sweep order is f[i] = y[i] / sqrt(deg[i]) (un-normalizing back to the
649    // walk eigenvector). Deterministic hash start (not a ramp -- ramps can align
650    // with a non-Fiedler mode) so a topology yields the same domains every boot.
651    // Cross-checked against domain_cut as ground truth (the T2 gate).
652    fn walk_cut(&self, members: &[usize]) -> Option<(Vec<usize>, Vec<usize>, f64)> {
653        let k = members.len();
654        if k < 2 {
655            return None;
656        }
657        let mut adj = vec![0.0f64; k * k];
658        let mut deg = vec![0.0f64; k];
659        for ia in 0..k {
660            for ib in 0..k {
661                if ia == ib {
662                    continue;
663                }
664                let w = self.conductance(members[ia], members[ib]);
665                adj[ia * k + ib] = w;
666                deg[ia] += w;
667            }
668        }
669        // D^{-1/2} and the stationary direction D^{1/2}*1 (L_sym's null vector).
670        let dis: Vec<f64> = deg
671            .iter()
672            .map(|&d| if d > 0.0 { 1.0 / d.sqrt() } else { 0.0 })
673            .collect();
674        let dsq: Vec<f64> = deg.iter().map(|&d| d.sqrt()).collect();
675        let dsq_sq: f64 = dsq.iter().map(|x| x * x).sum::<f64>().max(1e-30);
676        // Deflate out the stationary (null) component each step.
677        let deflate = |v: &mut [f64]| {
678            let dot: f64 = v.iter().zip(&dsq).map(|(a, b)| a * b).sum();
679            let coef = dot / dsq_sq;
680            for i in 0..k {
681                v[i] -= coef * dsq[i];
682            }
683        };
684        let mut y: Vec<f64> = (0..k)
685            .map(|i| {
686                let h = (i as u64).wrapping_mul(2654435761) & 0xffff;
687                h as f64 / 65535.0 - 0.5
688            })
689            .collect();
690        deflate(&mut y);
691        // A weak cut has a tiny normalized eigenvalue gap, so the per-iteration
692        // rate is near 1 and convergence can take thousands of steps -- iterate to
693        // CONVERGENCE (the direction stops moving), not a fixed count, capped high.
694        // Each step is O(k^2); a one-time boot cost, well under a millisecond.
695        const MAX_ITERS: usize = 20_000;
696        const TOL: f64 = 1e-10;
697        let mut w = vec![0.0f64; k];
698        for _ in 0..MAX_ITERS {
699            // (M y)[i] = y[i] + D^{-1/2}_i * sum_j A_ij * D^{-1/2}_j * y[j]
700            for i in 0..k {
701                let row = &adj[i * k..i * k + k];
702                let mut s = 0.0;
703                for j in 0..k {
704                    s += row[j] * dis[j] * y[j];
705                }
706                w[i] = y[i] + dis[i] * s;
707            }
708            deflate(&mut w);
709            let norm = w.iter().map(|x| x * x).sum::<f64>().sqrt();
710            if norm < 1e-12 {
711                break;
712            }
713            // cos angle between the new direction and the old unit vector y.
714            let dot: f64 = w.iter().zip(&y).map(|(a, b)| a * b).sum::<f64>() / norm;
715            for i in 0..k {
716                y[i] = w[i] / norm;
717            }
718            if 1.0 - dot.abs() < TOL {
719                break;
720            }
721        }
722        // Sweep order by the un-normalized walk eigenvector f[i] = y[i]/sqrt(deg).
723        let f: Vec<f64> = (0..k).map(|i| y[i] * dis[i]).collect();
724        let mut order: Vec<usize> = (0..k).collect();
725        order.sort_by(|&a, &b| f[a].partial_cmp(&f[b]).unwrap_or(std::cmp::Ordering::Equal));
726        let mut in_side = vec![false; self.nr_cpus];
727        let (mut best_phi, mut best_k) = (f64::INFINITY, 1usize);
728        for s in 1..k {
729            in_side[members[order[s - 1]]] = true;
730            let phi = self.cut_conductance(members, &in_side);
731            if phi < best_phi {
732                best_phi = phi;
733                best_k = s;
734            }
735        }
736        Some((
737            order[..best_k].iter().map(|&li| members[li]).collect(),
738            order[best_k..].iter().map(|&li| members[li]).collect(),
739            best_phi,
740        ))
741    }
742
743    // Dispatch: exact eigen cut below the threshold, scalable random-walk cut
744    // above. Both produce the same boundary on real cache graphs (gate-tested).
745    fn best_cut(&self, members: &[usize]) -> Option<(Vec<usize>, Vec<usize>, f64)> {
746        if members.len() <= Self::WALK_CUT_THRESHOLD {
747            self.domain_cut(members)
748        } else {
749            self.walk_cut(members)
750        }
751    }
752
753    // Recurse the cut into the emergent domain tree. Leaf when the members are a
754    // single L2 group (or one CPU) -- maximally coupled, no seam. Each Cut carries
755    // its phi (the crossing price). This IS de-facto NUMA: the boundary is drawn
756    // by the conductance landscape, not a hardcoded topology table.
757    pub fn build_domain_tree(&self, members: &[usize]) -> DomainNode {
758        if members.len() <= 1 || self.all_same_l2(members) {
759            return DomainNode::Leaf(members.to_vec());
760        }
761        match self.best_cut(members) {
762            Some((a, b, phi)) if !a.is_empty() && !b.is_empty() => DomainNode::Cut {
763                phi,
764                left: Box::new(self.build_domain_tree(&a)),
765                right: Box::new(self.build_domain_tree(&b)),
766            },
767            _ => DomainNode::Leaf(members.to_vec()),
768        }
769    }
770
771    // Compute the emergent domain tree over all online CPUs -- the de-facto-NUMA
772    // hierarchy from which T3's bounded-local steal reads its locality clusters
773    // and per-cut crossing prices. Public: the T2 -> T3 hand-off point.
774    pub fn compute_domain_tree(&self) -> DomainNode {
775        self.build_domain_tree(&(0..self.nr_cpus).collect::<Vec<_>>())
776    }
777
778    // Log the emergent domains at boot -- observability that the tree the steal
779    // will climb matches the silicon: atomic-domain count, cut depth, the
780    // crossing-price (phi) range, and the first leaves.
781    pub fn log_domains(&self, tree: &DomainNode) {
782        let leaves = tree.leaves();
783        let phis = tree.cut_phis();
784        let (pmin, pmax) = phis.iter().fold((f64::INFINITY, 0.0f64), |(lo, hi), &p| {
785            (lo.min(p), hi.max(p))
786        });
787        log_info!(
788            "EMERGENT DOMAINS: {} atomic, {} cuts, crossing phi {:.4}..{:.4}",
789            leaves.len(),
790            phis.len(),
791            if phis.is_empty() { 0.0 } else { pmin },
792            pmax
793        );
794        let preview: Vec<String> = leaves.iter().take(8).map(|l| format!("{:?}", l)).collect();
795        log_info!("EMERGENT DOMAINS: leaves {}", preview.join(" "));
796    }
797
798    // T3b.1: the per-CPU-pair crossing-price matrix the bounded steal reads.
799    // m[i*n + j] = (phi * 1e6) of the LCA cut separating CPU i and CPU j -- the
800    // price to steal across that emergent domain boundary. A LOW phi is a loose
801    // seam (a major boundary -- socket / cross-L3 -- far, needs more imbalance to
802    // cross); a HIGH phi is a tight seam (near). Same-leaf pairs share NO cut:
803    // sentinel u32::MAX = maximally local, the steal never has to "cross" for them.
804    // Replaces the discrete domain map's discrete same/different-cache domain test with a continuous,
805    // emergent boundary price (THE FLAG: priced, not gated).
806    pub fn domain_cross_phi_matrix(&self, tree: &DomainNode) -> Vec<u32> {
807        let n = self.nr_cpus;
808        let mut m = vec![u32::MAX; n * n]; // default: no boundary (same leaf)
809        Self::fill_cross_phi(tree, n, &mut m);
810        m
811    }
812
813    fn fill_cross_phi(node: &DomainNode, n: usize, m: &mut [u32]) {
814        if let DomainNode::Cut { phi, left, right } = node {
815            let lc: Vec<usize> = left.leaves().concat();
816            let rc: Vec<usize> = right.leaves().concat();
817            let p = (phi * 1_000_000.0).round().clamp(0.0, u32::MAX as f64) as u32;
818            for &a in &lc {
819                for &b in &rc {
820                    m[a * n + b] = p; // pairs whose lowest common ancestor IS this cut
821                    m[b * n + a] = p;
822                }
823            }
824            Self::fill_cross_phi(left, n, m);
825            Self::fill_cross_phi(right, n, m);
826        }
827    }
828
829    // Number of emergent OVERFLOW DOMAINS to target = distinct L3 groups, so
830    // re-keying the overflow DSQs preserves the L3 granularity.
831    pub fn overflow_domain_count(&self) -> usize {
832        let mut v = self.llc_domain.clone();
833        v.sort_unstable();
834        v.dedup();
835        v.len().max(1)
836    }
837
838    // T3b.2: partition CPUs into emergent OVERFLOW DOMAINS -- the the discrete domain map
839    // replacement. Descend the tree from the root, repeatedly splitting the
840    // frontier subtree whose cut has the LOWEST phi (the coarsest, most-separable
841    // seam) until `target` domains exist or no cut remains. Each resulting subtree
842    // is one overflow domain; dom[cpu] is its id. The granularity is the L3
843    // count; the boundary is drawn by the emergent tree, not the discrete domain map.
844    pub fn domain_partition(&self, tree: &DomainNode, target: usize) -> Vec<u32> {
845        let mut frontier: Vec<&DomainNode> = vec![tree];
846        while frontier.len() < target.max(1) {
847            let mut best: Option<(usize, f64)> = None;
848            for (i, node) in frontier.iter().enumerate() {
849                if let DomainNode::Cut { phi, .. } = node
850                    && best.is_none_or(|(_, bp)| *phi < bp)
851                {
852                    best = Some((i, *phi));
853                }
854            }
855            let Some((idx, _)) = best else { break }; // no cuts left to split
856            let node = frontier[idx];
857            if let DomainNode::Cut { left, right, .. } = node {
858                frontier.swap_remove(idx);
859                frontier.push(left.as_ref());
860                frontier.push(right.as_ref());
861            }
862        }
863        let n = self.nr_cpus;
864        let mut dom = vec![0u32; n];
865        for (id, node) in frontier.iter().enumerate() {
866            for leaf in node.leaves() {
867                for c in leaf {
868                    if c < n {
869                        dom[c] = id as u32;
870                    }
871                }
872            }
873        }
874        dom
875    }
876
877    // HOW MANY affinity_rank SLOTS STAY INSIDE A CPU'S OWN DOMAIN, MINIMUM OVER
878    // CPUs. COUNTED OFF THE SAME rank THE BPF WALK READS AND THE SAME cpu_dom THE
879    // OVERFLOW SITES RE-KEY ON, SO THE BOUND CANNOT DISAGREE WITH THE WALK.
880    //
881    // THE PREFIX IS WHAT MATTERS. build_affinity_rank SORTS BY R_eff ASCENDING AND
882    // THE CACHE RUNGS ARE ORDERED, SO A CPU'S SAME-DOMAIN PEERS OCCUPY AN UNBROKEN
883    // PREFIX -- THE COUNT IS THEREFORE THE INDEX OF THE FIRST CROSS-DOMAIN SLOT,
884    // WHICH IS THE BUDGET AT WHICH THE WALK LEAVES THE DOMAIN. THE LOOP STOPS AT
885    // THE FIRST NON-MEMBER RATHER THAN COUNTING MEMBERSHIP ANYWHERE IN THE ROW.
886    //
887    // MINIMUM, NOT AVERAGE. THE BOUND KEEPS THE WALK INSIDE EVERY CPU'S DOMAIN,
888    // SO THE TIGHTEST CPU SETS IT.
889    //
890    // A SINGLETON DOMAIN IS EXCLUDED RATHER THAN ALLOWED TO SET THE BOUND. A CPU
891    // WITH NO IN-DOMAIN PEER HAS NO BUDGET THAT COULD KEEP IT LOCAL, SO ITS ZERO
892    // WOULD DRAG EVERY OTHER CPU'S FLOOR TO 1 TO EXPRESS A CONSTRAINT THAT CANNOT
893    // HELP IT. THE SINGLETON FALLS THROUGH TO THE dfl PICK INSTEAD.
894    // RETURNS 1 ONLY WHEN NO CPU HAS A PEER.
895    pub fn min_in_domain_peers(&self, rank: &[u32], cpu_dom: &[u32]) -> u32 {
896        let n = self.nr_cpus;
897        if n < 2 || rank.len() < n * n || cpu_dom.len() < n {
898            return 1;
899        }
900        let mut min_peers = u32::MAX;
901        for cpu in 0..n {
902            let home = cpu_dom[cpu];
903            let mut peers = 0u32;
904            for slot in 0..(n - 1) {
905                let target = rank[cpu * n + slot] as usize;
906                if target >= n || cpu_dom[target] != home {
907                    break;
908                }
909                peers += 1;
910            }
911            if peers > 0 && peers < min_peers {
912                min_peers = peers;
913            }
914        }
915        if min_peers == u32::MAX { 1 } else { min_peers }
916    }
917
918    // SYMMETRIC EIGENDECOMPOSITION VIA JACOBI ROTATIONS
919    // RETURNS (eigenvalues, eigenvectors_column_major)
920    // SUITABLE FOR n <= 256. NO EXTERNAL DEPENDENCIES.
921    fn symmetric_eigen(mat: &[f64], n: usize) -> (Vec<f64>, Vec<f64>) {
922        let mut a = mat.to_vec();
923        // EIGENVECTORS START AS IDENTITY
924        let mut v = vec![0.0f64; n * n];
925        for i in 0..n {
926            v[i * n + i] = 1.0;
927        }
928
929        let max_iter = 100 * n * n;
930        for _ in 0..max_iter {
931            // FIND LARGEST OFF-DIAGONAL ELEMENT
932            let mut max_val = 0.0f64;
933            let mut p = 0;
934            let mut q = 1;
935            for i in 0..n {
936                for j in (i + 1)..n {
937                    let val = a[i * n + j].abs();
938                    if val > max_val {
939                        max_val = val;
940                        p = i;
941                        q = j;
942                    }
943                }
944            }
945            if max_val < 1e-12 {
946                break;
947            }
948
949            // COMPUTE ROTATION
950            let app = a[p * n + p];
951            let aqq = a[q * n + q];
952            let apq = a[p * n + q];
953            let theta = if (app - aqq).abs() < 1e-15 {
954                std::f64::consts::FRAC_PI_4
955            } else {
956                0.5 * (2.0 * apq / (app - aqq)).atan()
957            };
958            let c = theta.cos();
959            let s = theta.sin();
960
961            // APPLY ROTATION TO A
962            for i in 0..n {
963                if i == p || i == q {
964                    continue;
965                }
966                let aip = a[i * n + p];
967                let aiq = a[i * n + q];
968                a[i * n + p] = c * aip + s * aiq;
969                a[p * n + i] = a[i * n + p];
970                a[i * n + q] = -s * aip + c * aiq;
971                a[q * n + i] = a[i * n + q];
972            }
973            let new_pp = c * c * app + 2.0 * s * c * apq + s * s * aqq;
974            let new_qq = s * s * app - 2.0 * s * c * apq + c * c * aqq;
975            a[p * n + p] = new_pp;
976            a[q * n + q] = new_qq;
977            a[p * n + q] = 0.0;
978            a[q * n + p] = 0.0;
979
980            // ACCUMULATE EIGENVECTORS
981            for i in 0..n {
982                let vip = v[i * n + p];
983                let viq = v[i * n + q];
984                v[i * n + p] = c * vip + s * viq;
985                v[i * n + q] = -s * vip + c * viq;
986            }
987        }
988
989        let eigenvalues: Vec<f64> = (0..n).map(|i| a[i * n + i]).collect();
990        (eigenvalues, v)
991    }
992
993    // COMPUTE LAPLACIAN PSEUDOINVERSE FROM EIGENDECOMPOSITION
994    fn compute_pseudoinverse(eigenvalues: &[f64], eigenvectors: &[f64], n: usize) -> Vec<f64> {
995        let mut l_pinv = vec![0.0f64; n * n];
996        for k in 0..n {
997            if eigenvalues[k].abs() < 1e-8 {
998                continue; // SKIP NULL EIGENVALUE (CONNECTED GRAPH HAS ONE)
999            }
1000            let inv_lambda = 1.0 / eigenvalues[k];
1001            for i in 0..n {
1002                for j in 0..n {
1003                    l_pinv[i * n + j] +=
1004                        inv_lambda * eigenvectors[i * n + k] * eigenvectors[j * n + k];
1005                }
1006            }
1007        }
1008        l_pinv
1009    }
1010
1011    // COMPUTE ALL-PAIRS EFFECTIVE RESISTANCE FROM PSEUDOINVERSE
1012    // R_eff(i,j) = L+[i,i] + L+[j,j] - 2*L+[i,j]
1013    fn extract_reff(l_pinv: &[f64], n: usize) -> Vec<f64> {
1014        let mut r = vec![0.0f64; n * n];
1015        for i in 0..n {
1016            for j in (i + 1)..n {
1017                let val = l_pinv[i * n + i] + l_pinv[j * n + j] - 2.0 * l_pinv[i * n + j];
1018                r[i * n + j] = val.max(0.0);
1019                r[j * n + i] = r[i * n + j];
1020            }
1021        }
1022        r
1023    }
1024
1025    // BUILD PER-CPU AFFINITY RANK: FOR EACH CPU, ALL OTHERS SORTED BY R_EFF
1026    // Returns flat array: affinity_rank[cpu * nr_cpus + slot] = target_cpu
1027    fn build_affinity_rank(reff: &[f64], n: usize) -> Vec<u32> {
1028        let mut rank = vec![0u32; n * n];
1029        for cpu in 0..n {
1030            let mut others: Vec<(u64, u32)> = (0..n)
1031                .filter(|&c| c != cpu)
1032                .map(|c| {
1033                    // SORT KEY: R_EFF AS FIXED-POINT TO AVOID FLOAT COMPARISON ISSUES
1034                    let key = (reff[cpu * n + c] * 1_000_000.0) as u64;
1035                    (key, c as u32)
1036                })
1037                .collect();
1038            others.sort();
1039            for (slot, &(_, target)) in others.iter().enumerate() {
1040                rank[cpu * n + slot] = target;
1041            }
1042            // FILL REMAINING SLOTS WITH SENTINEL
1043            for slot in others.len()..n {
1044                rank[cpu * n + slot] = u32::MAX;
1045            }
1046        }
1047        rank
1048    }
1049
1050    // INTEGER LAPLACIAN FROM THE ICOND_* RUNG TABLE (SPILL-Phi). SAME EDGE
1051    // STRUCTURE AS build_laplacian, INTEGER WEIGHTS.
1052    fn build_laplacian_int(&self) -> Vec<i128> {
1053        let n = self.nr_cpus;
1054        let mut l = vec![0i128; n * n];
1055        for i in 0..n {
1056            for j in (i + 1)..n {
1057                let w = if self.l2_domain[i] == self.l2_domain[j] {
1058                    Self::ICOND_L2
1059                } else if self.llc_domain[i] == self.llc_domain[j] {
1060                    Self::ICOND_LLC
1061                } else if self.socket_domain[i] == self.socket_domain[j] {
1062                    Self::ICOND_SOCKET
1063                } else {
1064                    Self::ICOND_CROSS
1065                };
1066                l[i * n + j] = -w;
1067                l[j * n + i] = -w;
1068                l[i * n + i] += w;
1069                l[j * n + j] += w;
1070            }
1071        }
1072        l
1073    }
1074
1075    // BAREISS FRACTION-FREE DETERMINANT of an integer matrix (row-major, m x m).
1076    // Every division is exact by the Bareiss invariant. None on i128 overflow or a
1077    // zero pivot -- a connected reduced Laplacian is positive-definite, so a zero
1078    // pivot only ever means fall-back-to-float here, never a real singular case.
1079    pub fn bareiss_det(mut a: Vec<i128>, m: usize) -> Option<i128> {
1080        let mut prev: i128 = 1;
1081        for k in 0..m.saturating_sub(1) {
1082            let pivot = a[k * m + k];
1083            if pivot == 0 {
1084                return None;
1085            }
1086            for i in (k + 1)..m {
1087                for j in (k + 1)..m {
1088                    let t1 = a[i * m + j].checked_mul(pivot)?;
1089                    let t2 = a[i * m + k].checked_mul(a[k * m + j])?;
1090                    a[i * m + j] = t1.checked_sub(t2)? / prev;
1091                }
1092            }
1093            prev = pivot;
1094        }
1095        if m == 0 {
1096            Some(1)
1097        } else {
1098            Some(a[(m - 1) * m + (m - 1)])
1099        }
1100    }
1101
1102    // det of the integer Laplacian with the given index set's rows AND columns gone.
1103    fn minor_det(l_int: &[i128], n: usize, remove: &[usize]) -> Option<i128> {
1104        let keep: Vec<usize> = (0..n).filter(|x| !remove.contains(x)).collect();
1105        let m = keep.len();
1106        let mut sub = vec![0i128; m * m];
1107        for (a, &ri) in keep.iter().enumerate() {
1108            for (b, &cj) in keep.iter().enumerate() {
1109                sub[a * m + b] = l_int[ri * n + cj];
1110            }
1111        }
1112        Self::bareiss_det(sub, m)
1113    }
1114
1115    // EXACT R_eff(i,j) = det(L minus rows/cols i,j) / det(L minus row/col 0), the
1116    // spanning-2-forest / spanning-tree ratio (Barrett et al., Spanning 2-Forests
1117    // and Resistance Distance), in the integer Laplacian's units. None on overflow
1118    // so the caller falls back to the float path.
1119    pub fn reff_from_int_laplacian(l_int: &[i128], n: usize) -> Option<Vec<f64>> {
1120        if n < 2 {
1121            return Some(vec![0.0; n * n]);
1122        }
1123        let tau = Self::minor_det(l_int, n, &[0])?;
1124        if tau == 0 {
1125            return None;
1126        }
1127        let tau_f = tau as f64;
1128        let mut r = vec![0.0f64; n * n];
1129        for i in 0..n {
1130            for j in (i + 1)..n {
1131                let f = Self::minor_det(l_int, n, &[i, j])?;
1132                let v = ((f as f64) / tau_f).max(0.0);
1133                r[i * n + j] = v;
1134                r[j * n + i] = v;
1135            }
1136        }
1137        Some(r)
1138    }
1139
1140    // COMPUTE RESISTANCE AFFINITY: FULL PIPELINE
1141    // Returns (reff_matrix, affinity_rank, spectrum) for use by BPF and scheduler.
1142    // Spectrum carries lambda_2 (Fiedler value) and its derived tau_ns, used as
1143    // the universal topology time constant for every core-scaled knob.
1144    pub fn compute_resistance_affinity(&self) -> (Vec<f64>, Vec<u32>, TopologySpectrum) {
1145        let n = self.nr_cpus;
1146        let laplacian = self.build_laplacian();
1147        let (eigenvalues, eigenvectors) = Self::symmetric_eigen(&laplacian, n);
1148        let fiedler = extract_fiedler(&eigenvalues);
1149        let tau_ns = compute_tau_ns(fiedler, n);
1150        // EXACT INTEGER R_eff (Bareiss spanning-2-forest / spanning-tree determinant
1151        // ratio) where i128 holds it; else the float pseudoinverse. Same rung
1152        // ratios both paths, so downstream is unchanged either way.
1153        let reff: Vec<f64> = match (n <= Self::REFF_EXACT_MAX_CPUS)
1154            .then(|| Self::reff_from_int_laplacian(&self.build_laplacian_int(), n))
1155            .flatten()
1156        {
1157            Some(r) => r.into_iter().map(|x| x * Self::ICOND_RECOVER).collect(),
1158            None => {
1159                let l_pinv = Self::compute_pseudoinverse(&eigenvalues, &eigenvectors, n);
1160                Self::extract_reff(&l_pinv, n)
1161            }
1162        };
1163        let rank = Self::build_affinity_rank(&reff, n);
1164        let codel_eq_ns = compute_codel_eq_ns(&eigenvalues, n, tau_ns);
1165        // DISTANCE SCALE: calibrated so the most distant pair (max R_eff)
1166        // maps to ~tau of required steal-wait, an SMT sibling (R_eff ~ 0) to ~0. Only
1167        // sustained backlog (~tau) justifies a far move; a single queued slice does
1168        // not. reff_norm uses the same 1e6 scale the BPF reff_value map stores.
1169        // ALWAYS computed: on a single-L3 part the most distant pair is the cross-L2
1170        // (cross-core) max, so reff_norm auto-calibrates the brake to the L2 boundary
1171        // instead of vanishing -- the continuous metric drives placement on EVERY
1172        // processor, with no binary topology gate in front of it (THE FLAG).
1173        let max_reff = reff.iter().cloned().fold(0.0f64, f64::max);
1174        let reff_norm = ((max_reff * 1_000_000.0).round() as u64).max(1);
1175        let phi_dist_scale_q16 = tau_ns.saturating_mul(65536) / reff_norm;
1176        (
1177            reff,
1178            rank,
1179            TopologySpectrum {
1180                fiedler,
1181                tau_ns,
1182                codel_eq_ns,
1183                phi_dist_scale_q16,
1184            },
1185        )
1186    }
1187
1188    // WRITE AFFINITY RANK TO BPF MAP
1189    // affinity_rank[cpu * MAX_AFFINITY_CANDIDATES + slot] = target_cpu
1190    //
1191    // Emit the full sorted R_eff peer list per CPU, capped at the BPF
1192    // table width (MAX_AFFINITY_CANDIDATES). Slots beyond the actual
1193    // topology end (nr_cpus - 1) are written as explicit u32::MAX
1194    // sentinels so the BPF early-exit fires correctly -- map zero-init
1195    // would otherwise alias to "CPU 0" and silently mis-route.
1196    pub fn populate_affinity_rank_map(
1197        &self,
1198        sched: &Scheduler,
1199        reff: &[f64],
1200        rank: &[u32],
1201        phi_dist_scale_q16: u64,
1202        codel_eq_ns: u64,
1203        domain_phi: &[u32],
1204    ) -> Result<()> {
1205        let stride = crate::bpf_intf::MAX_AFFINITY_CANDIDATES as usize;
1206        let valid = self.nr_cpus.saturating_sub(1).min(stride);
1207        // R_eff's SPAN ACROSS DISTINCT PAIRS, FOR THE DIMENSIONLESS EMIT BELOW. A
1208        // distance prices only its excess over the nearest pair on the machine:
1209        // the nearest peer is free, the most distant pair is 65536, and a
1210        // topology whose pairs are all equidistant (2C on one L3) prices nothing.
1211        // The diagonal is excluded -- R_eff(i, i) is 0 and is no peer.
1212        let n = self.nr_cpus;
1213        let (mut rmin, mut rmax) = (f64::MAX, 0.0f64);
1214        for i in 0..n {
1215            for j in 0..n {
1216                if i != j {
1217                    let r = reff[i * n + j];
1218                    rmin = rmin.min(r);
1219                    rmax = rmax.max(r);
1220                }
1221            }
1222        }
1223        let to_scaled = |r: f64| (r * 1_000_000.0).round().clamp(0.0, u32::MAX as f64) as u64;
1224        let reff_floor = if rmin == f64::MAX { 0 } else { to_scaled(rmin) };
1225        let reff_span = to_scaled(rmax).saturating_sub(reff_floor);
1226        for cpu in 0..self.nr_cpus {
1227            for slot in 0..valid {
1228                let val = rank[cpu * self.nr_cpus + slot];
1229                sched.write_affinity_rank(cpu as u32, slot as u32, val)?;
1230                // T3b.1: the emergent-domain crossing price to this ranked peer,
1231                // 1:1 with the rank slot (sentinel for an out-of-range peer id).
1232                // IF phi_dist_scale_q16 IS 0 (tau UNSET; ITS FLOOR PREVENTS IT) THE STEAL
1233                // DOES NO R_eff DISTANCE PRICING; MAKE THE CROSSING PRICE
1234                // SENTINEL TOO SO THE STEAL-SIDE PAIR-SPLIT HOLD IS A NO-OP,
1235                // MATCHING reff_value's FLAT codel_target BASELINE (THE STEAL
1236                // IS domain_phi's CONSUMER).
1237                let dphi = if phi_dist_scale_q16 == 0 {
1238                    u32::MAX
1239                } else {
1240                    domain_phi
1241                        .get(cpu * self.nr_cpus + val as usize)
1242                        .copied()
1243                        .unwrap_or(u32::MAX)
1244                };
1245                sched.write_domain_phi(cpu as u32, slot as u32, dphi)?;
1246                // FOLD THE PHI DISTANCE PENALTY AT INIT: reff_value stores the
1247                // final steal extra-wait in ns, (R_eff * phi_dist_scale_q16) >> 16,
1248                // so the BPF steal does one indexed read and no multiply. The 1e6
1249                // scale matches build_affinity_rank's sort key. phi_dist_scale_q16
1250                // is computed on every part, a single-L3 one included; 0 only with tau unset -> flat
1251                // codel_target (exact prior behavior).
1252                // CEILING-ONLY CLAMP (shipped with SPILL-Phi; deliberately no
1253                // floor): an unclamped toll scales toward tau itself (~40ms,
1254                // the magnitude behind three hard freezes in the postmortem
1255                // record); cap it at 2*codel_eq_ns. NO floor -- dist_extra is a
1256                // per-peer price that must reach ~0 for a genuinely near peer (an
1257                // SMT sibling stays freely relievable), and STEP 1's phi_thresh
1258                // already adds the uniform codel_target_ns base on top.
1259                let r_scaled = (reff[cpu * self.nr_cpus + val as usize] * 1_000_000.0)
1260                    .round()
1261                    .clamp(0.0, u32::MAX as f64) as u64;
1262                let raw_extra = r_scaled.saturating_mul(phi_dist_scale_q16) >> 16;
1263                let dist_extra = if phi_dist_scale_q16 == 0 {
1264                    0
1265                } else {
1266                    raw_extra
1267                        .min(codel_eq_ns.saturating_mul(2))
1268                        .min(u32::MAX as u64) as u32
1269                };
1270                sched.write_reff_value(cpu as u32, slot as u32, dist_extra)?;
1271                // THE SAME R_eff SHAPE, WITH NO TIME UNIT ON IT. reff_value folds
1272                // the distance against TAU because that is the steal's question:
1273                // only sustained backlog (~tau) justifies a far pull. The wake-path
1274                // placement sites ask a different question against a comparand two
1275                // orders smaller -- one codel_target (233-667us) -- so the tau fold
1276                // is 20-170x oversized there and settles those tests by itself.
1277                // Emit the fraction instead and let each consumer multiply by the
1278                // yardstick its own comparison uses. Q16, so 65536 == the most
1279                // distant pair on this machine; bounded at every topology, which is
1280                // what stops slot 0 changing rung from moving a threshold 42x. The
1281                // nearest pair is 0, so a distance only ever prices how much
1282                // farther a peer is than the nearest one could have been.
1283                let frac_q16 = if phi_dist_scale_q16 == 0 || reff_span == 0 {
1284                    0u32
1285                } else {
1286                    ((r_scaled.saturating_sub(reff_floor) << 16) / reff_span).min(65536) as u32
1287                };
1288                sched.write_reff_frac(cpu as u32, slot as u32, frac_q16)?;
1289            }
1290            for slot in valid..stride {
1291                sched.write_affinity_rank(cpu as u32, slot as u32, u32::MAX)?;
1292                sched.write_reff_value(cpu as u32, slot as u32, u32::MAX)?;
1293                sched.write_reff_frac(cpu as u32, slot as u32, u32::MAX)?;
1294                sched.write_domain_phi(cpu as u32, slot as u32, u32::MAX)?;
1295            }
1296        }
1297        Ok(())
1298    }
1299
1300    pub fn log_resistance_affinity(&self, reff: &[f64], rank: &[u32], spectrum: TopologySpectrum) {
1301        log_info!(
1302            "TOPOLOGY SPECTRUM: lambda2={:.4} tau={}ms codel_eq={}us",
1303            spectrum.fiedler,
1304            spectrum.tau_ns / 1_000_000,
1305            spectrum.codel_eq_ns / 1_000
1306        );
1307        let n = self.nr_cpus;
1308        // LOG TOP 3 AFFINITIES FOR CPU 0
1309        let mut parts = Vec::new();
1310        for slot in 0..3.min(n - 1) {
1311            let target = rank[slot] as usize;
1312            if target >= n {
1313                break;
1314            }
1315            let r = reff[target];
1316            parts.push(format!("CPU{}(R={:.3})", target, r));
1317        }
1318        log_info!("RESISTANCE AFFINITY: CPU 0 rank: {}", parts.join(", "));
1319
1320        // LOG L2 VS NON-L2 R_EFF FOR FIRST CPU
1321        if n >= 2 {
1322            let l2_sib = rank[0] as usize;
1323            let non_l2 = rank[1.min(n - 2)] as usize;
1324            log_info!(
1325                "RESISTANCE AFFINITY: R_eff L2={:.4} non-L2={:.4} ratio={:.1}x",
1326                reff[l2_sib],
1327                reff[non_l2],
1328                if reff[l2_sib] > 0.0 {
1329                    reff[non_l2] / reff[l2_sib]
1330                } else {
1331                    0.0
1332                }
1333            );
1334        }
1335    }
1336
1337    pub fn log_summary(&self) {
1338        for (gid, members) in self.l2_groups.iter().enumerate() {
1339            let cpus: Vec<String> = members.iter().map(|c| c.to_string()).collect();
1340            log_info!("L2 GROUP {}: [{}]", gid, cpus.join(","));
1341        }
1342        log_info!(
1343            "L2 GROUPS: {} across {} CPUs, {} SOCKETS",
1344            self.l2_groups.len(),
1345            self.nr_cpus,
1346            self.nr_sockets
1347        );
1348        let mut llc = self.llc_domain.clone();
1349        llc.sort_unstable();
1350        llc.dedup();
1351        log_info!(
1352            "LLC DOMAINS: {} (L3 rung always-on, continuous Phi)",
1353            llc.len()
1354        );
1355    }
1356}
1357
1358// PARSE KERNEL CPU LIST FORMAT: "0,6" or "0-2,6-8" or "3"
1359fn parse_cpu_list(s: &str) -> Vec<u32> {
1360    let mut result = Vec::new();
1361    for part in s.split(',') {
1362        let part = part.trim();
1363        if part.is_empty() {
1364            continue;
1365        }
1366        if let Some((start, end)) = part.split_once('-') {
1367            if let (Ok(s), Ok(e)) = (start.parse::<u32>(), end.parse::<u32>()) {
1368                for cpu in s..=e {
1369                    result.push(cpu);
1370                }
1371            }
1372        } else if let Ok(cpu) = part.parse::<u32>() {
1373            result.push(cpu);
1374        }
1375    }
1376    result.sort();
1377    result.dedup();
1378    result
1379}