use crate::ir::{AccessPattern, SharingConflict, StructLayout};
pub fn normalize_guard(guard: &str) -> &str {
let s = guard
.strip_prefix("self.")
.or_else(|| guard.strip_prefix("this->"))
.or_else(|| guard.strip_prefix("this."))
.unwrap_or(guard);
s.trim_start_matches(['&', '*'])
}
pub fn find_sharing_conflicts(layout: &StructLayout) -> Vec<SharingConflict> {
let line = layout.arch.cache_line_size;
if line == 0 || layout.fields.is_empty() {
return Vec::new();
}
let mut buckets: std::collections::BTreeMap<usize, Vec<String>> =
std::collections::BTreeMap::new();
for field in &layout.fields {
if matches!(field.access, AccessPattern::Padding) {
continue;
}
let cl = field.offset / line;
buckets.entry(cl).or_default().push(field.name.clone());
}
buckets
.into_iter()
.filter(|(_, fields)| fields.len() > 1)
.map(|(cache_line, fields)| SharingConflict { fields, cache_line })
.collect()
}
pub fn has_false_sharing(layout: &StructLayout) -> bool {
let line = layout.arch.cache_line_size;
if line == 0 {
return false;
}
let concurrent: Vec<(usize, Option<&str>, bool)> = layout
.fields
.iter()
.filter_map(|f| {
if let AccessPattern::Concurrent {
guard, is_atomic, ..
} = &f.access
{
Some((f.offset / line, guard.as_deref(), *is_atomic))
} else {
None
}
})
.collect();
for i in 0..concurrent.len() {
for j in (i + 1)..concurrent.len() {
let (cl_a, guard_a, atomic_a) = concurrent[i];
let (cl_b, guard_b, atomic_b) = concurrent[j];
if cl_a == cl_b && guard_a.map(normalize_guard) != guard_b.map(normalize_guard) {
if atomic_a && atomic_b {
continue;
}
return true;
}
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::arch::X86_64_SYSV;
use crate::ir::{Field, StructLayout, TypeInfo};
fn make_layout(fields: Vec<Field>) -> StructLayout {
StructLayout {
name: "T".into(),
total_size: 128,
align: 8,
fields,
source_file: None,
source_line: None,
arch: &X86_64_SYSV,
is_packed: false,
is_union: false,
is_repr_rust: false,
suppressed_findings: Vec::new(),
uncertain_fields: Vec::new(),
}
}
fn concurrent(name: &str, offset: usize, guard: &str) -> Field {
Field {
name: name.into(),
ty: TypeInfo::Primitive {
name: "u64".into(),
size: 8,
align: 8,
},
offset,
size: 8,
align: 8,
source_file: None,
source_line: None,
access: AccessPattern::Concurrent {
guard: Some(guard.into()),
is_atomic: false,
is_annotated: false,
},
}
}
fn atomic(name: &str, offset: usize) -> Field {
Field {
name: name.into(),
ty: TypeInfo::Primitive {
name: "AtomicU64".into(),
size: 8,
align: 8,
},
offset,
size: 8,
align: 8,
source_file: None,
source_line: None,
access: AccessPattern::Concurrent {
guard: Some(name.into()),
is_atomic: true,
is_annotated: false,
},
}
}
fn plain(name: &str, offset: usize) -> Field {
Field {
name: name.into(),
ty: TypeInfo::Primitive {
name: "u64".into(),
size: 8,
align: 8,
},
offset,
size: 8,
align: 8,
source_file: None,
source_line: None,
access: AccessPattern::Unknown,
}
}
#[test]
fn two_fields_on_same_line_is_conflict() {
let layout = make_layout(vec![plain("a", 0), plain("b", 8)]);
let conflicts = find_sharing_conflicts(&layout);
assert_eq!(conflicts.len(), 1);
assert_eq!(conflicts[0].cache_line, 0);
}
#[test]
fn fields_on_different_lines_no_conflict() {
let layout = make_layout(vec![plain("a", 0), plain("b", 64)]);
assert!(find_sharing_conflicts(&layout).is_empty());
}
#[test]
fn has_false_sharing_when_different_guards_same_line() {
let layout = make_layout(vec![
concurrent("readers", 0, "lock_a"),
concurrent("writers", 8, "lock_b"),
]);
assert!(has_false_sharing(&layout));
}
#[test]
fn no_false_sharing_when_same_guard() {
let layout = make_layout(vec![concurrent("a", 0, "mu"), concurrent("b", 8, "mu")]);
assert!(!has_false_sharing(&layout));
}
#[test]
fn no_false_sharing_when_all_unknown() {
let layout = make_layout(vec![plain("a", 0), plain("b", 8)]);
assert!(!has_false_sharing(&layout));
}
#[test]
fn no_false_sharing_when_different_lines() {
let layout = make_layout(vec![
concurrent("a", 0, "lock_a"),
concurrent("b", 64, "lock_b"),
]);
assert!(!has_false_sharing(&layout));
}
#[test]
fn no_false_sharing_for_two_pure_atomics_same_line() {
let layout = make_layout(vec![atomic("counter_a", 0), atomic("counter_b", 8)]);
assert!(!has_false_sharing(&layout));
}
#[test]
fn false_sharing_for_atomic_and_mutex_same_line() {
let layout = make_layout(vec![
atomic("hot_counter", 0),
concurrent("protected_data", 8, "mu"),
]);
assert!(has_false_sharing(&layout));
}
#[test]
fn false_sharing_detected_with_mixed_atomics_and_mutex() {
let layout = make_layout(vec![
atomic("reads", 0),
atomic("writes", 8),
concurrent("state", 16, "mu"),
]);
assert!(has_false_sharing(&layout));
}
#[test]
fn no_false_sharing_for_pure_atomics_on_different_lines() {
let layout = make_layout(vec![atomic("counter_a", 0), atomic("counter_b", 64)]);
assert!(!has_false_sharing(&layout));
}
#[test]
fn normalize_strips_self_prefix() {
assert_eq!(normalize_guard("self.mu"), "mu");
}
#[test]
fn normalize_strips_this_arrow_prefix() {
assert_eq!(normalize_guard("this->mu"), "mu");
}
#[test]
fn normalize_strips_this_dot_prefix() {
assert_eq!(normalize_guard("this.mu"), "mu");
}
#[test]
fn normalize_strips_leading_ampersand() {
assert_eq!(normalize_guard("&mu"), "mu");
}
#[test]
fn normalize_strips_leading_star() {
assert_eq!(normalize_guard("*mu"), "mu");
}
#[test]
fn normalize_no_change_for_plain_name() {
assert_eq!(normalize_guard("mu"), "mu");
}
#[test]
fn no_false_sharing_when_guards_differ_only_by_self_prefix() {
let layout = make_layout(vec![
concurrent("readers", 0, "self.mu"),
concurrent("writers", 8, "mu"),
]);
assert!(!has_false_sharing(&layout));
}
#[test]
fn no_false_sharing_when_guards_differ_only_by_this_arrow_prefix() {
let layout = make_layout(vec![
concurrent("readers", 0, "this->lock"),
concurrent("writers", 8, "lock"),
]);
assert!(!has_false_sharing(&layout));
}
}