Skip to main content

padlock_core/analysis/
false_sharing.rs

1// padlock-core/src/analysis/false_sharing.rs
2
3use crate::ir::{AccessPattern, SharingConflict, StructLayout};
4
5/// Return all groups of fields that share a cache line.
6/// Any cache line with two or more fields is a potential false-sharing hazard.
7pub 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
30/// Return `true` if any cache line contains two or more `Concurrent` fields
31/// with *different* lock guards — a confirmed false-sharing hazard.
32pub 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// ── tests ─────────────────────────────────────────────────────────────────────
63
64#[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}