Skip to main content

padlock_core/analysis/
locality.rs

1// padlock-core/src/analysis/locality.rs
2
3use crate::ir::{AccessPattern, Field, StructLayout};
4
5pub struct FieldLocality<'a> {
6    pub field: &'a Field,
7    pub is_hot: bool,
8}
9
10fn is_hot(f: &Field) -> bool {
11    matches!(
12        f.access,
13        AccessPattern::ReadMostly | AccessPattern::Concurrent { .. }
14    )
15}
16
17/// Classify every field as hot or cold.
18pub fn classify_fields(layout: &StructLayout) -> Vec<FieldLocality<'_>> {
19    layout
20        .fields
21        .iter()
22        .map(|f| FieldLocality {
23            field: f,
24            is_hot: is_hot(f),
25        })
26        .collect()
27}
28
29/// Returns `true` if hot and cold fields are interleaved (a locality problem).
30/// If all fields are hot or all are cold the layout is fine.
31pub fn has_locality_issue(layout: &StructLayout) -> bool {
32    let classified = classify_fields(layout);
33    let has_hot = classified.iter().any(|c| c.is_hot);
34    let has_cold = classified.iter().any(|c| !c.is_hot);
35    if !has_hot || !has_cold {
36        return false;
37    }
38    // Scan for a cold→hot or hot→cold→hot transition (interleaving).
39    let mut saw_cold_after_hot = false;
40    let mut last_was_hot = false;
41    for c in &classified {
42        if c.is_hot {
43            if saw_cold_after_hot {
44                return true; // hot field appears after a cold field that followed a hot field
45            }
46            last_was_hot = true;
47        } else {
48            if last_was_hot {
49                saw_cold_after_hot = true;
50            }
51        }
52    }
53    false
54}
55
56/// Split fields into (hot_names, cold_names) preserving original order.
57pub fn partition_hot_cold(layout: &StructLayout) -> (Vec<String>, Vec<String>) {
58    let mut hot = Vec::new();
59    let mut cold = Vec::new();
60    for f in &layout.fields {
61        if is_hot(f) {
62            hot.push(f.name.clone());
63        } else {
64            cold.push(f.name.clone());
65        }
66    }
67    (hot, cold)
68}
69
70// ── tests ─────────────────────────────────────────────────────────────────────
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use crate::arch::X86_64_SYSV;
76    use crate::ir::{Field, StructLayout, TypeInfo};
77
78    fn field(name: &str, offset: usize, access: AccessPattern) -> Field {
79        Field {
80            name: name.into(),
81            ty: TypeInfo::Primitive {
82                name: "u64".into(),
83                size: 8,
84                align: 8,
85            },
86            offset,
87            size: 8,
88            align: 8,
89            source_file: None,
90            source_line: None,
91            access,
92        }
93    }
94
95    fn layout(fields: Vec<Field>) -> StructLayout {
96        StructLayout {
97            name: "T".into(),
98            total_size: fields.len() * 8,
99            align: 8,
100            fields,
101            source_file: None,
102            source_line: None,
103            arch: &X86_64_SYSV,
104            is_packed: false,
105            is_union: false,
106            is_repr_rust: false,
107        }
108    }
109
110    #[test]
111    fn interleaved_hot_cold_is_issue() {
112        // hot cold hot — locality issue
113        let l = layout(vec![
114            field("a", 0, AccessPattern::ReadMostly),
115            field("b", 8, AccessPattern::Unknown),
116            field("c", 16, AccessPattern::ReadMostly),
117        ]);
118        assert!(has_locality_issue(&l));
119    }
120
121    #[test]
122    fn hot_first_then_cold_is_fine() {
123        let l = layout(vec![
124            field("a", 0, AccessPattern::ReadMostly),
125            field("b", 8, AccessPattern::ReadMostly),
126            field("c", 16, AccessPattern::Unknown),
127            field("d", 24, AccessPattern::Unknown),
128        ]);
129        assert!(!has_locality_issue(&l));
130    }
131
132    #[test]
133    fn all_unknown_no_issue() {
134        let l = layout(vec![
135            field("a", 0, AccessPattern::Unknown),
136            field("b", 8, AccessPattern::Unknown),
137        ]);
138        assert!(!has_locality_issue(&l));
139    }
140
141    #[test]
142    fn partition_separates_correctly() {
143        let l = layout(vec![
144            field("a", 0, AccessPattern::ReadMostly),
145            field("b", 8, AccessPattern::Unknown),
146            field(
147                "c",
148                16,
149                AccessPattern::Concurrent {
150                    guard: None,
151                    is_atomic: true,
152                },
153            ),
154        ]);
155        let (hot, cold) = partition_hot_cold(&l);
156        assert_eq!(hot, vec!["a", "c"]);
157        assert_eq!(cold, vec!["b"]);
158    }
159}