struct_audit/analysis/
padding.rs

1use crate::types::{LayoutMetrics, PaddingHole, StructLayout};
2
3pub fn analyze_layout(layout: &mut StructLayout, cache_line_size: u32) {
4    #[derive(Clone)]
5    struct Span {
6        start: u64,
7        end: u64,
8        member_name: String,
9    }
10
11    let mut spans = Vec::new();
12    let mut partial = false;
13
14    for member in &layout.members {
15        let Some(member_offset) = member.offset else {
16            partial = true;
17            continue;
18        };
19        let Some(member_size) = member.size else {
20            partial = true;
21            continue;
22        };
23        if member_size == 0 {
24            continue;
25        }
26
27        spans.push(Span {
28            start: member_offset,
29            end: member_offset.saturating_add(member_size),
30            member_name: member.name.clone(),
31        });
32    }
33
34    spans.sort_by_key(|s| (s.start, s.end));
35
36    let mut padding_holes = Vec::new();
37    let mut useful_size: u64 = 0;
38
39    // Can't infer padding without at least one reliable span.
40    if spans.is_empty() {
41        let cache_line_size_u64 = cache_line_size as u64;
42        let cache_lines_spanned =
43            if layout.size > 0 { layout.size.div_ceil(cache_line_size_u64) as u32 } else { 0 };
44
45        layout.metrics = LayoutMetrics {
46            total_size: layout.size,
47            useful_size: 0,
48            padding_bytes: 0,
49            padding_percentage: 0.0,
50            cache_lines_spanned,
51            cache_line_density: 0.0,
52            padding_holes,
53            partial,
54        };
55        return;
56    }
57
58    // Merge overlapping spans (bitfields share storage, unions overlap, etc.). We use the merged
59    // covered bytes for "useful_size" to avoid double-counting overlapping members.
60    let mut current_start = spans[0].start;
61    let mut current_end = spans[0].end;
62    let mut current_end_member: Option<String> = Some(spans[0].member_name.clone());
63
64    for span in spans.into_iter().skip(1) {
65        if span.start > current_end {
66            useful_size = useful_size.saturating_add(current_end.saturating_sub(current_start));
67
68            if !partial {
69                padding_holes.push(PaddingHole {
70                    offset: current_end,
71                    size: span.start - current_end,
72                    after_member: current_end_member.clone(),
73                });
74            }
75
76            current_start = span.start;
77            current_end = span.end;
78            current_end_member = Some(span.member_name);
79            continue;
80        }
81
82        if span.end >= current_end {
83            current_end = span.end;
84            current_end_member = Some(span.member_name);
85        }
86    }
87
88    useful_size = useful_size.saturating_add(current_end.saturating_sub(current_start));
89
90    if !partial && current_end < layout.size {
91        padding_holes.push(PaddingHole {
92            offset: current_end,
93            size: layout.size - current_end,
94            after_member: current_end_member,
95        });
96    }
97
98    let padding_bytes: u64 = padding_holes.iter().map(|h| h.size).sum();
99    let padding_percentage =
100        if layout.size > 0 { (padding_bytes as f64 / layout.size as f64) * 100.0 } else { 0.0 };
101
102    let cache_line_size_u64 = cache_line_size as u64;
103    let cache_lines_spanned =
104        if layout.size > 0 { layout.size.div_ceil(cache_line_size_u64) as u32 } else { 0 };
105
106    let total_cache_bytes = cache_lines_spanned as u64 * cache_line_size_u64;
107    let cache_line_density = if total_cache_bytes > 0 {
108        (useful_size as f64 / total_cache_bytes as f64) * 100.0
109    } else {
110        0.0
111    };
112
113    layout.metrics = LayoutMetrics {
114        total_size: layout.size,
115        useful_size,
116        padding_bytes,
117        padding_percentage,
118        cache_lines_spanned,
119        cache_line_density,
120        padding_holes,
121        partial,
122    };
123}