1use crate::types::{
2 AtomicMember, CacheLineSpanningWarning, FalseSharingAnalysis, FalseSharingWarning, StructLayout,
3};
4use std::collections::BTreeMap;
5
6const ATOMIC_PATTERNS: &[&str] = &[
7 "std::sync::atomic::Atomic",
9 "core::sync::atomic::Atomic",
10 "std::sync::Mutex",
12 "std::sync::RwLock",
13 "std::sync::Condvar",
14 "std::sync::Once",
15 "std::sync::OnceLock",
16 "std::sync::Barrier",
17 "std::atomic<",
19 "std::__1::atomic<",
20 "std::__cxx11::atomic<",
21 "_Atomic ",
23 "_Atomic(",
24 "parking_lot::Mutex",
26 "parking_lot::RwLock",
27 "parking_lot::Once",
28 "parking_lot::Condvar",
29 "parking_lot::ReentrantMutex",
30 "parking_lot::FairMutex",
31 "parking_lot::RawMutex",
32 "parking_lot::RawRwLock",
33 "crossbeam::atomic::AtomicCell",
35 "crossbeam_utils::atomic::AtomicCell",
36 "crossbeam_epoch::Atomic",
37 "atomic_refcell::AtomicRefCell",
39 "tokio::sync::Mutex",
41 "tokio::sync::RwLock",
42 "tokio::sync::Semaphore",
43 "tokio::sync::Notify",
44 "tokio::sync::Barrier",
45 "tokio::sync::OnceCell",
46 "arc_swap::ArcSwap",
48 "arc_swap::ArcSwapOption",
49 "arc_swap::ArcSwapAny",
50];
51
52fn is_atomic_type_by_name(type_name: &str) -> bool {
53 ATOMIC_PATTERNS.iter().any(|pattern| type_name.contains(pattern))
54}
55
56pub fn analyze_false_sharing(layout: &StructLayout, cache_line_size: u32) -> FalseSharingAnalysis {
61 assert!(cache_line_size > 0, "cache_line_size must be > 0");
62 let cache_line_size_u64 = cache_line_size as u64;
63
64 let atomic_members: Vec<AtomicMember> = layout
65 .members
66 .iter()
67 .filter(|m| m.is_atomic || is_atomic_type_by_name(&m.type_name))
69 .filter_map(|m| {
70 let offset = m.offset?;
71 let size = m.size?;
72 if size == 0 {
73 return None;
74 }
75 let cache_line = offset / cache_line_size_u64;
76 let Some(end_offset) = offset.checked_add(size).and_then(|v| v.checked_sub(1)) else {
78 return None; };
80 let end_cache_line = end_offset / cache_line_size_u64;
81 let spans_cache_lines = end_cache_line > cache_line;
82
83 Some(AtomicMember {
84 name: m.name.clone(),
85 type_name: m.type_name.clone(),
86 offset,
87 size,
88 cache_line,
89 end_cache_line,
90 spans_cache_lines,
91 })
92 })
93 .collect();
94
95 if atomic_members.is_empty() {
96 return FalseSharingAnalysis::default();
97 }
98
99 let spanning_warnings: Vec<CacheLineSpanningWarning> = atomic_members
101 .iter()
102 .filter(|m| m.spans_cache_lines)
103 .map(|m| CacheLineSpanningWarning {
104 member: m.name.clone(),
105 type_name: m.type_name.clone(),
106 offset: m.offset,
107 size: m.size,
108 start_cache_line: m.cache_line,
109 end_cache_line: m.end_cache_line,
110 lines_spanned: m.end_cache_line - m.cache_line + 1,
111 })
112 .collect();
113
114 if atomic_members.len() < 2 {
115 return FalseSharingAnalysis { atomic_members, warnings: Vec::new(), spanning_warnings };
116 }
117
118 let mut by_cache_line: BTreeMap<u64, Vec<&AtomicMember>> = BTreeMap::new();
121 for member in &atomic_members {
122 for cache_line in member.cache_line..=member.end_cache_line {
123 by_cache_line.entry(cache_line).or_default().push(member);
124 }
125 }
126
127 let mut warnings = Vec::new();
128 let mut seen_pairs: std::collections::HashSet<(&str, &str)> = std::collections::HashSet::new();
129
130 for (cache_line, members) in &by_cache_line {
131 if members.len() < 2 {
132 continue;
133 }
134
135 for i in 0..members.len() {
136 for j in (i + 1)..members.len() {
137 let a = members[i];
138 let b = members[j];
139
140 let (first, second) = if a.offset <= b.offset { (a, b) } else { (b, a) };
142
143 let pair_key = (first.name.as_str(), second.name.as_str());
144 if seen_pairs.contains(&pair_key) {
145 continue;
146 }
147 seen_pairs.insert(pair_key);
148
149 let first_end = first.offset.saturating_add(first.size);
152 let gap_bytes = second.offset as i64 - first_end as i64;
153
154 warnings.push(FalseSharingWarning {
155 member_a: first.name.clone(),
156 member_b: second.name.clone(),
157 cache_line: *cache_line,
158 gap_bytes,
159 });
160 }
161 }
162 }
163
164 warnings.sort_by(|a, b| {
166 a.cache_line
167 .cmp(&b.cache_line)
168 .then_with(|| a.member_a.cmp(&b.member_a))
169 .then_with(|| a.member_b.cmp(&b.member_b))
170 });
171
172 FalseSharingAnalysis { atomic_members, warnings, spanning_warnings }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use crate::types::MemberLayout;
179
180 fn make_layout_with_members(members: Vec<MemberLayout>) -> StructLayout {
181 let mut layout = StructLayout::new("TestStruct".to_string(), 128, Some(8));
182 layout.members = members;
183 layout
184 }
185
186 #[test]
187 fn test_two_atomics_same_cache_line() {
188 let layout = make_layout_with_members(vec![
189 MemberLayout::new(
190 "counter".to_string(),
191 "std::sync::atomic::AtomicU64".to_string(),
192 Some(0),
193 Some(8),
194 ),
195 MemberLayout::new(
196 "flag".to_string(),
197 "std::sync::atomic::AtomicBool".to_string(),
198 Some(8),
199 Some(1),
200 ),
201 ]);
202
203 let analysis = analyze_false_sharing(&layout, 64);
204
205 assert_eq!(analysis.atomic_members.len(), 2);
206 assert_eq!(analysis.warnings.len(), 1);
207 assert_eq!(analysis.warnings[0].cache_line, 0);
208 assert_eq!(analysis.warnings[0].member_a, "counter");
209 assert_eq!(analysis.warnings[0].member_b, "flag");
210 assert_eq!(analysis.warnings[0].gap_bytes, 0); }
212
213 #[test]
214 fn test_two_atomics_different_cache_lines() {
215 let layout = make_layout_with_members(vec![
216 MemberLayout::new(
217 "counter1".to_string(),
218 "std::sync::atomic::AtomicU64".to_string(),
219 Some(0),
220 Some(8),
221 ),
222 MemberLayout::new(
223 "counter2".to_string(),
224 "std::sync::atomic::AtomicU64".to_string(),
225 Some(64),
226 Some(8),
227 ),
228 ]);
229
230 let analysis = analyze_false_sharing(&layout, 64);
231
232 assert_eq!(analysis.atomic_members.len(), 2);
233 assert!(analysis.warnings.is_empty());
234 }
235
236 #[test]
237 fn test_three_atomics_same_cache_line() {
238 let layout = make_layout_with_members(vec![
239 MemberLayout::new(
240 "a".to_string(),
241 "std::sync::atomic::AtomicU64".to_string(),
242 Some(0),
243 Some(8),
244 ),
245 MemberLayout::new(
246 "b".to_string(),
247 "std::sync::atomic::AtomicU64".to_string(),
248 Some(8),
249 Some(8),
250 ),
251 MemberLayout::new(
252 "c".to_string(),
253 "std::sync::atomic::AtomicU64".to_string(),
254 Some(16),
255 Some(8),
256 ),
257 ]);
258
259 let analysis = analyze_false_sharing(&layout, 64);
260
261 assert_eq!(analysis.atomic_members.len(), 3);
262 assert_eq!(analysis.warnings.len(), 3); }
264
265 #[test]
266 fn test_non_atomic_ignored() {
267 let layout = make_layout_with_members(vec![
268 MemberLayout::new(
269 "counter".to_string(),
270 "std::sync::atomic::AtomicU64".to_string(),
271 Some(0),
272 Some(8),
273 ),
274 MemberLayout::new("data".to_string(), "u64".to_string(), Some(8), Some(8)),
275 ]);
276
277 let analysis = analyze_false_sharing(&layout, 64);
278
279 assert_eq!(analysis.atomic_members.len(), 1);
280 assert!(analysis.warnings.is_empty());
281 }
282
283 #[test]
284 fn test_cpp_atomic_detection() {
285 let layout = make_layout_with_members(vec![
286 MemberLayout::new("a".to_string(), "std::atomic<int>".to_string(), Some(0), Some(4)),
287 MemberLayout::new("b".to_string(), "std::atomic<int>".to_string(), Some(4), Some(4)),
288 ]);
289
290 let analysis = analyze_false_sharing(&layout, 64);
291
292 assert_eq!(analysis.atomic_members.len(), 2);
293 assert_eq!(analysis.warnings.len(), 1);
294 }
295
296 #[test]
297 fn test_c11_atomic_detection() {
298 let layout = make_layout_with_members(vec![
299 MemberLayout::new("a".to_string(), "_Atomic int".to_string(), Some(0), Some(4)),
300 MemberLayout::new("b".to_string(), "_Atomic int".to_string(), Some(4), Some(4)),
301 ]);
302
303 let analysis = analyze_false_sharing(&layout, 64);
304
305 assert_eq!(analysis.atomic_members.len(), 2);
306 assert_eq!(analysis.warnings.len(), 1);
307 }
308
309 #[test]
310 fn test_parking_lot_detection() {
311 let layout = make_layout_with_members(vec![
312 MemberLayout::new(
313 "lock1".to_string(),
314 "parking_lot::Mutex<T>".to_string(),
315 Some(0),
316 Some(8),
317 ),
318 MemberLayout::new(
319 "lock2".to_string(),
320 "parking_lot::RwLock<T>".to_string(),
321 Some(8),
322 Some(16),
323 ),
324 ]);
325
326 let analysis = analyze_false_sharing(&layout, 64);
327
328 assert_eq!(analysis.atomic_members.len(), 2);
329 assert_eq!(analysis.warnings.len(), 1);
330 }
331
332 #[test]
335 fn test_std_sync_mutex_detection() {
336 let layout = make_layout_with_members(vec![
337 MemberLayout::new(
338 "lock1".to_string(),
339 "std::sync::Mutex<i32>".to_string(),
340 Some(0),
341 Some(16),
342 ),
343 MemberLayout::new(
344 "lock2".to_string(),
345 "std::sync::RwLock<i32>".to_string(),
346 Some(16),
347 Some(24),
348 ),
349 ]);
350
351 let analysis = analyze_false_sharing(&layout, 64);
352
353 assert_eq!(analysis.atomic_members.len(), 2);
354 assert_eq!(analysis.warnings.len(), 1);
355 }
356
357 #[test]
358 fn test_single_atomic_no_warnings() {
359 let layout = make_layout_with_members(vec![MemberLayout::new(
360 "counter".to_string(),
361 "std::sync::atomic::AtomicU64".to_string(),
362 Some(0),
363 Some(8),
364 )]);
365
366 let analysis = analyze_false_sharing(&layout, 64);
367
368 assert_eq!(analysis.atomic_members.len(), 1);
369 assert!(analysis.warnings.is_empty());
370 assert!(analysis.spanning_warnings.is_empty());
371 }
372
373 #[test]
374 fn test_zero_size_atomic_ignored() {
375 let layout = make_layout_with_members(vec![
376 MemberLayout::new(
377 "counter".to_string(),
378 "std::sync::atomic::AtomicU64".to_string(),
379 Some(0),
380 Some(8),
381 ),
382 MemberLayout::new(
383 "zst".to_string(),
384 "std::sync::atomic::AtomicUnit".to_string(), Some(8),
386 Some(0),
387 ),
388 ]);
389
390 let analysis = analyze_false_sharing(&layout, 64);
391
392 assert_eq!(analysis.atomic_members.len(), 1);
393 assert!(analysis.warnings.is_empty());
394 }
395
396 #[test]
397 fn test_c11_atomic_parenthesized() {
398 let layout = make_layout_with_members(vec![
399 MemberLayout::new("a".to_string(), "_Atomic(int)".to_string(), Some(0), Some(4)),
400 MemberLayout::new("b".to_string(), "_Atomic(int)".to_string(), Some(4), Some(4)),
401 ]);
402
403 let analysis = analyze_false_sharing(&layout, 64);
404
405 assert_eq!(analysis.atomic_members.len(), 2);
406 assert_eq!(analysis.warnings.len(), 1);
407 }
408
409 #[test]
410 fn test_tokio_sync_detection() {
411 let layout = make_layout_with_members(vec![
412 MemberLayout::new(
413 "lock1".to_string(),
414 "tokio::sync::Mutex<i32>".to_string(),
415 Some(0),
416 Some(16),
417 ),
418 MemberLayout::new(
419 "lock2".to_string(),
420 "tokio::sync::RwLock<i32>".to_string(),
421 Some(16),
422 Some(24),
423 ),
424 ]);
425
426 let analysis = analyze_false_sharing(&layout, 64);
427
428 assert_eq!(analysis.atomic_members.len(), 2);
429 assert_eq!(analysis.warnings.len(), 1);
430 }
431
432 #[test]
435 fn test_atomic_spanning_cache_lines() {
436 let layout = make_layout_with_members(vec![MemberLayout::new(
438 "spanning".to_string(),
439 "std::sync::atomic::AtomicU64".to_string(),
440 Some(60),
441 Some(8),
442 )]);
443
444 let analysis = analyze_false_sharing(&layout, 64);
445
446 assert_eq!(analysis.atomic_members.len(), 1);
447 assert!(analysis.atomic_members[0].spans_cache_lines);
448 assert_eq!(analysis.atomic_members[0].cache_line, 0);
449 assert_eq!(analysis.atomic_members[0].end_cache_line, 1);
450
451 assert_eq!(analysis.spanning_warnings.len(), 1);
452 assert_eq!(analysis.spanning_warnings[0].member, "spanning");
453 assert_eq!(analysis.spanning_warnings[0].lines_spanned, 2);
454 }
455
456 #[test]
457 fn test_atomic_not_spanning() {
458 let layout = make_layout_with_members(vec![MemberLayout::new(
460 "aligned".to_string(),
461 "std::sync::atomic::AtomicU64".to_string(),
462 Some(0),
463 Some(8),
464 )]);
465
466 let analysis = analyze_false_sharing(&layout, 64);
467
468 assert_eq!(analysis.atomic_members.len(), 1);
469 assert!(!analysis.atomic_members[0].spans_cache_lines);
470 assert!(analysis.spanning_warnings.is_empty());
471 }
472
473 #[test]
474 fn test_gap_bytes_calculation() {
475 let layout = make_layout_with_members(vec![
476 MemberLayout::new(
477 "a".to_string(),
478 "std::sync::atomic::AtomicU64".to_string(),
479 Some(0),
480 Some(8),
481 ),
482 MemberLayout::new(
483 "b".to_string(),
484 "std::sync::atomic::AtomicU64".to_string(),
485 Some(16), Some(8),
487 ),
488 ]);
489
490 let analysis = analyze_false_sharing(&layout, 64);
491
492 assert_eq!(analysis.warnings.len(), 1);
493 assert_eq!(analysis.warnings[0].gap_bytes, 8); }
495
496 #[test]
497 fn test_gap_bytes_adjacent() {
498 let layout = make_layout_with_members(vec![
499 MemberLayout::new(
500 "a".to_string(),
501 "std::sync::atomic::AtomicU64".to_string(),
502 Some(0),
503 Some(8),
504 ),
505 MemberLayout::new(
506 "b".to_string(),
507 "std::sync::atomic::AtomicU64".to_string(),
508 Some(8), Some(8),
510 ),
511 ]);
512
513 let analysis = analyze_false_sharing(&layout, 64);
514
515 assert_eq!(analysis.warnings.len(), 1);
516 assert_eq!(analysis.warnings[0].gap_bytes, 0); }
518
519 #[test]
520 fn test_spanning_atomic_shares_with_both_lines() {
521 let layout = make_layout_with_members(vec![
525 MemberLayout::new(
526 "spanning".to_string(),
527 "std::sync::atomic::AtomicU64".to_string(),
528 Some(60),
529 Some(8),
530 ),
531 MemberLayout::new(
532 "other".to_string(),
533 "std::sync::atomic::AtomicU64".to_string(),
534 Some(70),
535 Some(8),
536 ),
537 ]);
538
539 let analysis = analyze_false_sharing(&layout, 64);
540
541 assert_eq!(analysis.atomic_members.len(), 2);
542 assert_eq!(analysis.warnings.len(), 1);
544 assert_eq!(analysis.warnings[0].cache_line, 1);
545 }
546
547 #[test]
550 fn test_no_atomics_returns_default() {
551 let layout = make_layout_with_members(vec![
553 MemberLayout::new("x".to_string(), "u64".to_string(), Some(0), Some(8)),
554 MemberLayout::new("y".to_string(), "u64".to_string(), Some(8), Some(8)),
555 ]);
556
557 let analysis = analyze_false_sharing(&layout, 64);
558
559 assert!(analysis.atomic_members.is_empty());
560 assert!(analysis.warnings.is_empty());
561 assert!(analysis.spanning_warnings.is_empty());
562 }
563
564 #[test]
565 fn test_overflow_offset_skipped() {
566 let layout = make_layout_with_members(vec![
569 MemberLayout::new(
570 "normal".to_string(),
571 "std::sync::atomic::AtomicU64".to_string(),
572 Some(0),
573 Some(8),
574 ),
575 MemberLayout::new(
576 "overflow".to_string(),
577 "std::sync::atomic::AtomicU64".to_string(),
578 Some(u64::MAX - 3), Some(8),
580 ),
581 ]);
582
583 let analysis = analyze_false_sharing(&layout, 64);
584
585 assert_eq!(analysis.atomic_members.len(), 1);
587 assert_eq!(analysis.atomic_members[0].name, "normal");
588 assert!(analysis.warnings.is_empty());
589 }
590
591 #[test]
592 fn test_duplicate_pair_dedupe_deterministic() {
593 let layout = make_layout_with_members(vec![
597 MemberLayout::new(
598 "spanning_a".to_string(),
599 "std::sync::atomic::AtomicU64".to_string(),
600 Some(60), Some(8),
602 ),
603 MemberLayout::new(
604 "spanning_b".to_string(),
605 "std::sync::atomic::AtomicU64".to_string(),
606 Some(62), Some(8),
608 ),
609 ]);
610
611 let analysis = analyze_false_sharing(&layout, 64);
612
613 assert_eq!(analysis.atomic_members.len(), 2);
614 assert_eq!(analysis.warnings.len(), 1);
616 assert_eq!(analysis.warnings[0].cache_line, 0);
618 assert_eq!(analysis.warnings[0].member_a, "spanning_a");
619 assert_eq!(analysis.warnings[0].member_b, "spanning_b");
620 }
621
622 #[test]
623 fn test_empty_layout_returns_default() {
624 let layout = make_layout_with_members(vec![]);
626
627 let analysis = analyze_false_sharing(&layout, 64);
628
629 assert!(analysis.atomic_members.is_empty());
630 assert!(analysis.warnings.is_empty());
631 assert!(analysis.spanning_warnings.is_empty());
632 }
633}