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 =
48 if layout.size > 0 { layout.size.div_ceil(cache_line_size_u64) as u32 } else { 0 };
49
50 layout.metrics = LayoutMetrics {
51 total_size: layout.size,
52 useful_size: 0,
53 padding_bytes: 0,
54 padding_percentage: 0.0,
55 cache_lines_spanned,
56 cache_line_density: 0.0,
57 padding_holes,
58 partial,
59 false_sharing: None,
60 };
61 return;
62 }
63
64 let mut current_start = spans[0].start;
67 let mut current_end = spans[0].end;
68 let mut current_end_member: Option<String> = Some(spans[0].member_name.clone());
69
70 for span in spans.into_iter().skip(1) {
71 if span.start > current_end {
72 useful_size = useful_size.saturating_add(current_end.saturating_sub(current_start));
73
74 if !partial {
75 padding_holes.push(PaddingHole {
76 offset: current_end,
77 size: span.start - current_end,
78 after_member: current_end_member.clone(),
79 });
80 }
81
82 current_start = span.start;
83 current_end = span.end;
84 current_end_member = Some(span.member_name);
85 continue;
86 }
87
88 if span.end >= current_end {
89 current_end = span.end;
90 current_end_member = Some(span.member_name);
91 }
92 }
93
94 useful_size = useful_size.saturating_add(current_end.saturating_sub(current_start));
95
96 if !partial && current_end < layout.size {
97 padding_holes.push(PaddingHole {
98 offset: current_end,
99 size: layout.size - current_end,
100 after_member: current_end_member,
101 });
102 }
103
104 let padding_bytes: u64 = padding_holes.iter().map(|h| h.size).sum();
105 let padding_percentage =
106 if layout.size > 0 { (padding_bytes as f64 / layout.size as f64) * 100.0 } else { 0.0 };
107
108 let cache_line_size_u64 = cache_line_size as u64;
109 let cache_lines_spanned =
110 if layout.size > 0 { layout.size.div_ceil(cache_line_size_u64) as u32 } else { 0 };
111
112 let total_cache_bytes = cache_lines_spanned as u64 * cache_line_size_u64;
113 let cache_line_density = if total_cache_bytes > 0 {
114 (useful_size as f64 / total_cache_bytes as f64) * 100.0
115 } else {
116 0.0
117 };
118
119 layout.metrics = LayoutMetrics {
120 total_size: layout.size,
121 useful_size,
122 padding_bytes,
123 padding_percentage,
124 cache_lines_spanned,
125 cache_line_density,
126 padding_holes,
127 partial,
128 false_sharing: None,
129 };
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use crate::types::MemberLayout;
136
137 fn make_layout(size: u64, members: Vec<MemberLayout>) -> StructLayout {
138 let mut layout = StructLayout::new("TestStruct".to_string(), size, Some(8));
139 layout.members = members;
140 layout
141 }
142
143 #[test]
144 fn test_empty_members_no_spans() {
145 let mut layout = make_layout(64, vec![]);
147
148 analyze_layout(&mut layout, 64);
149
150 assert_eq!(layout.metrics.total_size, 64);
151 assert_eq!(layout.metrics.useful_size, 0);
152 assert_eq!(layout.metrics.padding_bytes, 0);
153 assert_eq!(layout.metrics.padding_percentage, 0.0);
154 assert_eq!(layout.metrics.cache_lines_spanned, 1);
155 assert!(!layout.metrics.partial);
156 assert!(layout.metrics.padding_holes.is_empty());
157 }
158
159 #[test]
160 fn test_zero_size_layout_no_spans() {
161 let mut layout = make_layout(0, vec![]);
163
164 analyze_layout(&mut layout, 64);
165
166 assert_eq!(layout.metrics.total_size, 0);
167 assert_eq!(layout.metrics.cache_lines_spanned, 0);
168 assert_eq!(layout.metrics.cache_line_density, 0.0);
169 }
170
171 #[test]
172 fn test_partial_layout_missing_offset() {
173 let mut layout = make_layout(
175 16,
176 vec![
177 MemberLayout::new("a".to_string(), "u64".to_string(), Some(0), Some(8)),
178 MemberLayout::new("b".to_string(), "u64".to_string(), None, Some(8)), ],
180 );
181
182 analyze_layout(&mut layout, 64);
183
184 assert!(layout.metrics.partial);
185 assert!(layout.metrics.padding_holes.is_empty());
187 }
188
189 #[test]
190 fn test_partial_layout_missing_size() {
191 let mut layout = make_layout(
193 16,
194 vec![
195 MemberLayout::new("a".to_string(), "u64".to_string(), Some(0), Some(8)),
196 MemberLayout::new("b".to_string(), "u64".to_string(), Some(8), None), ],
198 );
199
200 analyze_layout(&mut layout, 64);
201
202 assert!(layout.metrics.partial);
203 assert!(layout.metrics.padding_holes.is_empty());
204 }
205
206 #[test]
207 fn test_zero_size_member_skipped() {
208 let mut layout = make_layout(
210 16,
211 vec![
212 MemberLayout::new("a".to_string(), "u64".to_string(), Some(0), Some(8)),
213 MemberLayout::new("zst".to_string(), "()".to_string(), Some(8), Some(0)), MemberLayout::new("b".to_string(), "u64".to_string(), Some(8), Some(8)),
215 ],
216 );
217
218 analyze_layout(&mut layout, 64);
219
220 assert!(!layout.metrics.partial);
221 assert_eq!(layout.metrics.useful_size, 16);
222 }
223
224 #[test]
225 fn test_overlapping_spans_merged() {
226 let mut layout = make_layout(
228 16,
229 vec![
230 MemberLayout::new("a".to_string(), "u64".to_string(), Some(0), Some(16)),
231 MemberLayout::new("b".to_string(), "u64".to_string(), Some(4), Some(8)), ],
233 );
234
235 analyze_layout(&mut layout, 64);
236
237 assert_eq!(layout.metrics.useful_size, 16);
239 assert!(layout.metrics.padding_holes.is_empty());
240 }
241
242 #[test]
243 fn test_padding_hole_detected() {
244 let mut layout = make_layout(
246 24,
247 vec![
248 MemberLayout::new("a".to_string(), "u64".to_string(), Some(0), Some(8)),
249 MemberLayout::new("b".to_string(), "u64".to_string(), Some(16), Some(8)),
251 ],
252 );
253
254 analyze_layout(&mut layout, 64);
255
256 assert_eq!(layout.metrics.padding_bytes, 8);
257 assert_eq!(layout.metrics.padding_holes.len(), 1);
258 assert_eq!(layout.metrics.padding_holes[0].offset, 8);
259 assert_eq!(layout.metrics.padding_holes[0].size, 8);
260 }
261
262 #[test]
263 fn test_tail_padding_detected() {
264 let mut layout = make_layout(
266 16,
267 vec![MemberLayout::new("a".to_string(), "u64".to_string(), Some(0), Some(8))],
268 );
269
270 analyze_layout(&mut layout, 64);
271
272 assert_eq!(layout.metrics.padding_bytes, 8);
273 assert_eq!(layout.metrics.padding_holes.len(), 1);
274 assert_eq!(layout.metrics.padding_holes[0].offset, 8);
275 }
276
277 #[test]
278 fn test_partial_layout_no_tail_padding_reported() {
279 let mut layout = make_layout(
281 32,
282 vec![
283 MemberLayout::new("a".to_string(), "u64".to_string(), Some(0), Some(8)),
284 MemberLayout::new("b".to_string(), "u64".to_string(), None, Some(8)), ],
286 );
287
288 analyze_layout(&mut layout, 64);
289
290 assert!(layout.metrics.partial);
291 assert!(layout.metrics.padding_holes.is_empty());
293 }
294}