1use crate::types::{LayoutMetrics, PaddingHole, StructLayout};
2
3pub fn analyze_layout(layout: &mut StructLayout, cache_line_size: u32) {
8 assert!(cache_line_size > 0, "cache_line_size must be > 0");
9 #[derive(Clone)]
10 struct Span {
11 start: u64,
12 end: u64,
13 member_name: String,
14 }
15
16 let mut spans = Vec::new();
17 let mut partial = false;
18
19 for member in &layout.members {
20 let Some(member_offset) = member.offset else {
21 partial = true;
22 continue;
23 };
24 let Some(member_size) = member.size else {
25 partial = true;
26 continue;
27 };
28 if member_size == 0 {
29 continue;
30 }
31
32 spans.push(Span {
33 start: member_offset,
34 end: member_offset.saturating_add(member_size),
35 member_name: member.name.clone(),
36 });
37 }
38
39 spans.sort_by_key(|s| (s.start, s.end));
40
41 let mut padding_holes = Vec::new();
42 let mut useful_size: u64 = 0;
43
44 if spans.is_empty() {
46 let cache_line_size_u64 = cache_line_size as u64;
47 let cache_lines_spanned = if layout.size > 0 {
49 layout.size.div_ceil(cache_line_size_u64).min(u32::MAX as u64) as u32
50 } else {
51 0
52 };
53
54 layout.metrics = LayoutMetrics {
55 total_size: layout.size,
56 useful_size: 0,
57 padding_bytes: 0,
58 padding_percentage: 0.0,
59 cache_lines_spanned,
60 cache_line_density: 0.0,
61 padding_holes,
62 partial,
63 false_sharing: None,
64 };
65 return;
66 }
67
68 let mut current_start = spans[0].start;
71 let mut current_end = spans[0].end;
72 let mut current_end_member: Option<String> = Some(spans[0].member_name.clone());
73
74 for span in spans.into_iter().skip(1) {
75 if span.start > current_end {
76 useful_size = useful_size.saturating_add(current_end.saturating_sub(current_start));
77
78 if !partial {
79 padding_holes.push(PaddingHole {
80 offset: current_end,
81 size: span.start - current_end,
82 after_member: current_end_member.clone(),
83 });
84 }
85
86 current_start = span.start;
87 current_end = span.end;
88 current_end_member = Some(span.member_name);
89 continue;
90 }
91
92 if span.end >= current_end {
93 current_end = span.end;
94 current_end_member = Some(span.member_name);
95 }
96 }
97
98 useful_size = useful_size.saturating_add(current_end.saturating_sub(current_start));
99
100 if !partial && current_end < layout.size {
101 padding_holes.push(PaddingHole {
102 offset: current_end,
103 size: layout.size - current_end,
104 after_member: current_end_member,
105 });
106 }
107
108 let padding_bytes: u64 = padding_holes.iter().map(|h| h.size).sum();
109 let padding_percentage =
110 if layout.size > 0 { (padding_bytes as f64 / layout.size as f64) * 100.0 } else { 0.0 };
111
112 let cache_line_size_u64 = cache_line_size as u64;
113 let cache_lines_spanned = if layout.size > 0 {
115 layout.size.div_ceil(cache_line_size_u64).min(u32::MAX as u64) as u32
116 } else {
117 0
118 };
119
120 let total_cache_bytes = cache_lines_spanned as u64 * cache_line_size_u64;
121 let cache_line_density = if total_cache_bytes > 0 {
122 (useful_size as f64 / total_cache_bytes as f64) * 100.0
123 } else {
124 0.0
125 };
126
127 layout.metrics = LayoutMetrics {
128 total_size: layout.size,
129 useful_size,
130 padding_bytes,
131 padding_percentage,
132 cache_lines_spanned,
133 cache_line_density,
134 padding_holes,
135 partial,
136 false_sharing: None,
137 };
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use crate::types::MemberLayout;
144
145 fn make_layout(size: u64, members: Vec<MemberLayout>) -> StructLayout {
146 let mut layout = StructLayout::new("TestStruct".to_string(), size, Some(8));
147 layout.members = members;
148 layout
149 }
150
151 #[test]
152 fn test_empty_members_no_spans() {
153 let mut layout = make_layout(64, vec![]);
155
156 analyze_layout(&mut layout, 64);
157
158 assert_eq!(layout.metrics.total_size, 64);
159 assert_eq!(layout.metrics.useful_size, 0);
160 assert_eq!(layout.metrics.padding_bytes, 0);
161 assert_eq!(layout.metrics.padding_percentage, 0.0);
162 assert_eq!(layout.metrics.cache_lines_spanned, 1);
163 assert!(!layout.metrics.partial);
164 assert!(layout.metrics.padding_holes.is_empty());
165 }
166
167 #[test]
168 fn test_zero_size_layout_no_spans() {
169 let mut layout = make_layout(0, vec![]);
171
172 analyze_layout(&mut layout, 64);
173
174 assert_eq!(layout.metrics.total_size, 0);
175 assert_eq!(layout.metrics.cache_lines_spanned, 0);
176 assert_eq!(layout.metrics.cache_line_density, 0.0);
177 }
178
179 #[test]
180 fn test_partial_layout_missing_offset() {
181 let mut layout = make_layout(
183 16,
184 vec![
185 MemberLayout::new("a".to_string(), "u64".to_string(), Some(0), Some(8)),
186 MemberLayout::new("b".to_string(), "u64".to_string(), None, Some(8)), ],
188 );
189
190 analyze_layout(&mut layout, 64);
191
192 assert!(layout.metrics.partial);
193 assert!(layout.metrics.padding_holes.is_empty());
195 }
196
197 #[test]
198 fn test_partial_layout_missing_size() {
199 let mut layout = make_layout(
201 16,
202 vec![
203 MemberLayout::new("a".to_string(), "u64".to_string(), Some(0), Some(8)),
204 MemberLayout::new("b".to_string(), "u64".to_string(), Some(8), None), ],
206 );
207
208 analyze_layout(&mut layout, 64);
209
210 assert!(layout.metrics.partial);
211 assert!(layout.metrics.padding_holes.is_empty());
212 }
213
214 #[test]
215 fn test_zero_size_member_skipped() {
216 let mut layout = make_layout(
218 16,
219 vec![
220 MemberLayout::new("a".to_string(), "u64".to_string(), Some(0), Some(8)),
221 MemberLayout::new("zst".to_string(), "()".to_string(), Some(8), Some(0)), MemberLayout::new("b".to_string(), "u64".to_string(), Some(8), Some(8)),
223 ],
224 );
225
226 analyze_layout(&mut layout, 64);
227
228 assert!(!layout.metrics.partial);
229 assert_eq!(layout.metrics.useful_size, 16);
230 }
231
232 #[test]
233 fn test_overlapping_spans_merged() {
234 let mut layout = make_layout(
236 16,
237 vec![
238 MemberLayout::new("a".to_string(), "u64".to_string(), Some(0), Some(16)),
239 MemberLayout::new("b".to_string(), "u64".to_string(), Some(4), Some(8)), ],
241 );
242
243 analyze_layout(&mut layout, 64);
244
245 assert_eq!(layout.metrics.useful_size, 16);
247 assert!(layout.metrics.padding_holes.is_empty());
248 }
249
250 #[test]
251 fn test_padding_hole_detected() {
252 let mut layout = make_layout(
254 24,
255 vec![
256 MemberLayout::new("a".to_string(), "u64".to_string(), Some(0), Some(8)),
257 MemberLayout::new("b".to_string(), "u64".to_string(), Some(16), Some(8)),
259 ],
260 );
261
262 analyze_layout(&mut layout, 64);
263
264 assert_eq!(layout.metrics.padding_bytes, 8);
265 assert_eq!(layout.metrics.padding_holes.len(), 1);
266 assert_eq!(layout.metrics.padding_holes[0].offset, 8);
267 assert_eq!(layout.metrics.padding_holes[0].size, 8);
268 }
269
270 #[test]
271 fn test_tail_padding_detected() {
272 let mut layout = make_layout(
274 16,
275 vec![MemberLayout::new("a".to_string(), "u64".to_string(), Some(0), Some(8))],
276 );
277
278 analyze_layout(&mut layout, 64);
279
280 assert_eq!(layout.metrics.padding_bytes, 8);
281 assert_eq!(layout.metrics.padding_holes.len(), 1);
282 assert_eq!(layout.metrics.padding_holes[0].offset, 8);
283 }
284
285 #[test]
286 fn test_partial_layout_no_tail_padding_reported() {
287 let mut layout = make_layout(
289 32,
290 vec![
291 MemberLayout::new("a".to_string(), "u64".to_string(), Some(0), Some(8)),
292 MemberLayout::new("b".to_string(), "u64".to_string(), None, Some(8)), ],
294 );
295
296 analyze_layout(&mut layout, 64);
297
298 assert!(layout.metrics.partial);
299 assert!(layout.metrics.padding_holes.is_empty());
301 }
302}