padlock_core/analysis/
locality.rs1use 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
17pub 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
29pub 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 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; }
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
56pub 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#[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 suppressed_findings: Vec::new(),
108 }
109 }
110
111 #[test]
112 fn interleaved_hot_cold_is_issue() {
113 let l = layout(vec![
115 field("a", 0, AccessPattern::ReadMostly),
116 field("b", 8, AccessPattern::Unknown),
117 field("c", 16, AccessPattern::ReadMostly),
118 ]);
119 assert!(has_locality_issue(&l));
120 }
121
122 #[test]
123 fn hot_first_then_cold_is_fine() {
124 let l = layout(vec![
125 field("a", 0, AccessPattern::ReadMostly),
126 field("b", 8, AccessPattern::ReadMostly),
127 field("c", 16, AccessPattern::Unknown),
128 field("d", 24, AccessPattern::Unknown),
129 ]);
130 assert!(!has_locality_issue(&l));
131 }
132
133 #[test]
134 fn all_unknown_no_issue() {
135 let l = layout(vec![
136 field("a", 0, AccessPattern::Unknown),
137 field("b", 8, AccessPattern::Unknown),
138 ]);
139 assert!(!has_locality_issue(&l));
140 }
141
142 #[test]
143 fn partition_separates_correctly() {
144 let l = layout(vec![
145 field("a", 0, AccessPattern::ReadMostly),
146 field("b", 8, AccessPattern::Unknown),
147 field(
148 "c",
149 16,
150 AccessPattern::Concurrent {
151 guard: None,
152 is_atomic: true,
153 is_annotated: false,
154 },
155 ),
156 ]);
157 let (hot, cold) = partition_hot_cold(&l);
158 assert_eq!(hot, vec!["a", "c"]);
159 assert_eq!(cold, vec!["b"]);
160 }
161}