padlock_core/analysis/
false_sharing.rs1use crate::ir::{AccessPattern, SharingConflict, StructLayout};
4
5pub fn find_sharing_conflicts(layout: &StructLayout) -> Vec<SharingConflict> {
8 let line = layout.arch.cache_line_size;
9 if line == 0 || layout.fields.is_empty() {
10 return Vec::new();
11 }
12
13 let mut buckets: std::collections::BTreeMap<usize, Vec<String>> =
14 std::collections::BTreeMap::new();
15 for field in &layout.fields {
16 if matches!(field.access, AccessPattern::Padding) {
17 continue;
18 }
19 let cl = field.offset / line;
20 buckets.entry(cl).or_default().push(field.name.clone());
21 }
22
23 buckets
24 .into_iter()
25 .filter(|(_, fields)| fields.len() > 1)
26 .map(|(cache_line, fields)| SharingConflict { fields, cache_line })
27 .collect()
28}
29
30pub fn has_false_sharing(layout: &StructLayout) -> bool {
33 let line = layout.arch.cache_line_size;
34 if line == 0 {
35 return false;
36 }
37
38 let concurrent: Vec<(usize, Option<&str>)> = layout
39 .fields
40 .iter()
41 .filter_map(|f| {
42 if let AccessPattern::Concurrent { guard, .. } = &f.access {
43 Some((f.offset / line, guard.as_deref()))
44 } else {
45 None
46 }
47 })
48 .collect();
49
50 for i in 0..concurrent.len() {
51 for j in (i + 1)..concurrent.len() {
52 let (cl_a, guard_a) = concurrent[i];
53 let (cl_b, guard_b) = concurrent[j];
54 if cl_a == cl_b && guard_a != guard_b {
55 return true;
56 }
57 }
58 }
59 false
60}
61
62#[cfg(test)]
65mod tests {
66 use super::*;
67 use crate::arch::X86_64_SYSV;
68 use crate::ir::{Field, StructLayout, TypeInfo};
69
70 fn make_layout(fields: Vec<Field>) -> StructLayout {
71 StructLayout {
72 name: "T".into(),
73 total_size: 128,
74 align: 8,
75 fields,
76 source_file: None,
77 source_line: None,
78 arch: &X86_64_SYSV,
79 is_packed: false,
80 is_union: false,
81 is_repr_rust: false,
82 }
83 }
84
85 fn concurrent(name: &str, offset: usize, guard: &str) -> Field {
86 Field {
87 name: name.into(),
88 ty: TypeInfo::Primitive {
89 name: "u64".into(),
90 size: 8,
91 align: 8,
92 },
93 offset,
94 size: 8,
95 align: 8,
96 source_file: None,
97 source_line: None,
98 access: AccessPattern::Concurrent {
99 guard: Some(guard.into()),
100 is_atomic: false,
101 },
102 }
103 }
104
105 fn plain(name: &str, offset: usize) -> Field {
106 Field {
107 name: name.into(),
108 ty: TypeInfo::Primitive {
109 name: "u64".into(),
110 size: 8,
111 align: 8,
112 },
113 offset,
114 size: 8,
115 align: 8,
116 source_file: None,
117 source_line: None,
118 access: AccessPattern::Unknown,
119 }
120 }
121
122 #[test]
123 fn two_fields_on_same_line_is_conflict() {
124 let layout = make_layout(vec![plain("a", 0), plain("b", 8)]);
125 let conflicts = find_sharing_conflicts(&layout);
126 assert_eq!(conflicts.len(), 1);
127 assert_eq!(conflicts[0].cache_line, 0);
128 }
129
130 #[test]
131 fn fields_on_different_lines_no_conflict() {
132 let layout = make_layout(vec![plain("a", 0), plain("b", 64)]);
133 assert!(find_sharing_conflicts(&layout).is_empty());
134 }
135
136 #[test]
137 fn has_false_sharing_when_different_guards_same_line() {
138 let layout = make_layout(vec![
139 concurrent("readers", 0, "lock_a"),
140 concurrent("writers", 8, "lock_b"),
141 ]);
142 assert!(has_false_sharing(&layout));
143 }
144
145 #[test]
146 fn no_false_sharing_when_same_guard() {
147 let layout = make_layout(vec![concurrent("a", 0, "mu"), concurrent("b", 8, "mu")]);
148 assert!(!has_false_sharing(&layout));
149 }
150
151 #[test]
152 fn no_false_sharing_when_all_unknown() {
153 let layout = make_layout(vec![plain("a", 0), plain("b", 8)]);
154 assert!(!has_false_sharing(&layout));
155 }
156
157 #[test]
158 fn no_false_sharing_when_different_lines() {
159 let layout = make_layout(vec![
160 concurrent("a", 0, "lock_a"),
161 concurrent("b", 64, "lock_b"),
162 ]);
163 assert!(!has_false_sharing(&layout));
164 }
165}