openentropy_core/sources/microarch/
icc_atomic_contention.rs1use std::sync::Arc;
33use std::sync::atomic::{AtomicU64, Ordering};
34use std::thread;
35
36use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
37use crate::sources::helpers::{extract_timing_entropy, mach_time};
38
39static ICC_ATOMIC_CONTENTION_INFO: SourceInfo = SourceInfo {
40 name: "icc_atomic_contention",
41 description: "Apple Silicon ICC bus arbitration timing via cross-core atomic contention",
42 physics: "Two threads race to atomically increment the same cache line. Each \
43 increment requires the ICC coherency fabric to transfer the cache line \
44 between cores via MESI invalidation+transfer. The arbitration traverses \
45 the ICC bus, which carries all coherency traffic from all running processes \
46 on the chip. Measured: CV=191\u{2013}195%, range 0\u{2013}209 ticks (0ns\u{2013}8.7\u{00b5}s). \
47 LSB bias of 0.188 is a microarchitectural constant: ICC coherency transfers \
48 always complete in even hardware tick counts.",
49 category: SourceCategory::Microarch,
50 platform: Platform::MacOS,
51 requirements: &[],
52 entropy_rate_estimate: 2.5,
53 composite: false,
54 is_fast: false,
55};
56
57pub struct ICCAtomicContentionSource;
59
60impl EntropySource for ICCAtomicContentionSource {
61 fn info(&self) -> &SourceInfo {
62 &ICC_ATOMIC_CONTENTION_INFO
63 }
64
65 fn is_available(&self) -> bool {
66 cfg!(target_os = "macos")
69 }
70
71 fn collect(&self, n_samples: usize) -> Vec<u8> {
72 let raw_per_thread = n_samples * 8 + 64;
75
76 let shared = Arc::new(AtomicU64::new(0));
80 let shared2 = shared.clone();
81
82 let ready = Arc::new(AtomicU64::new(0));
84 let ready2 = ready.clone();
85
86 let thread_timings: Arc<std::sync::Mutex<Vec<u64>>> =
87 Arc::new(std::sync::Mutex::new(Vec::with_capacity(raw_per_thread)));
88 let thread_timings2 = thread_timings.clone();
89
90 let raw = raw_per_thread;
92 let handle = thread::spawn(move || {
93 ready2.store(1, Ordering::Release);
95
96 let mut local: Vec<u64> = Vec::with_capacity(raw);
97
98 for _ in 0..32 {
100 shared2.fetch_add(1, Ordering::SeqCst);
101 }
102
103 for _ in 0..raw {
104 let t0 = mach_time();
105 shared2.fetch_add(1, Ordering::SeqCst);
106 let elapsed = mach_time().wrapping_sub(t0);
107 local.push(elapsed);
108 }
109
110 *thread_timings2.lock().unwrap() = local;
111 });
112
113 while ready.load(Ordering::Acquire) == 0 {
115 thread::yield_now();
116 }
117
118 let mut main_timings: Vec<u64> = Vec::with_capacity(raw_per_thread);
120
121 for _ in 0..32 {
123 shared.fetch_add(1, Ordering::SeqCst);
124 }
125
126 for _ in 0..raw_per_thread {
127 let t0 = mach_time();
128 shared.fetch_add(1, Ordering::SeqCst);
129 let elapsed = mach_time().wrapping_sub(t0);
130 if elapsed < 240_000 {
132 main_timings.push(elapsed);
133 }
134 }
135
136 let _ = handle.join();
137
138 let contender_timings = thread_timings.lock().unwrap_or_else(|e| e.into_inner());
142 let mut combined: Vec<u64> =
143 Vec::with_capacity(main_timings.len() + contender_timings.len());
144 let min_len = main_timings.len().min(contender_timings.len());
145 for i in 0..min_len {
146 combined.push(main_timings[i] ^ contender_timings[i]);
148 combined.push(main_timings[i].wrapping_add(contender_timings[i]));
149 }
150
151 extract_timing_entropy(&combined, n_samples)
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn info() {
161 let src = ICCAtomicContentionSource;
162 assert_eq!(src.info().name, "icc_atomic_contention");
163 assert!(matches!(src.info().category, SourceCategory::Microarch));
164 assert_eq!(src.info().platform, Platform::MacOS);
165 assert!(!src.info().composite);
166 }
167
168 #[test]
169 fn is_available() {
170 assert!(ICCAtomicContentionSource.is_available());
171 }
172
173 #[test]
174 #[ignore] fn collects_bytes() {
176 let data = ICCAtomicContentionSource.collect(32);
177 assert!(!data.is_empty());
178 let unique: std::collections::HashSet<u8> = data.iter().copied().collect();
179 assert!(unique.len() > 4);
180 }
181}