1use crate::analysis::{false_sharing, locality, padding, reorder, scorer};
4use crate::ir::{PaddingGap, SharingConflict, StructLayout};
5
6#[derive(Debug, Clone, PartialEq, serde::Serialize)]
7pub enum Severity {
8 Low,
9 Medium,
10 High,
11}
12
13#[derive(Debug, Clone, serde::Serialize)]
14#[serde(tag = "kind")]
15pub enum Finding {
16 PaddingWaste {
17 struct_name: String,
18 total_size: usize,
19 wasted_bytes: usize,
20 waste_pct: f64,
21 gaps: Vec<PaddingGap>,
22 severity: Severity,
23 },
24 FalseSharing {
25 struct_name: String,
26 conflicts: Vec<SharingConflict>,
27 severity: Severity,
28 },
29 ReorderSuggestion {
30 struct_name: String,
31 original_size: usize,
32 optimized_size: usize,
33 savings: usize,
34 suggested_order: Vec<String>,
35 severity: Severity,
36 },
37 LocalityIssue {
38 struct_name: String,
39 hot_fields: Vec<String>,
40 cold_fields: Vec<String>,
41 severity: Severity,
42 },
43}
44
45impl Finding {
46 pub fn severity(&self) -> &Severity {
47 match self {
48 Finding::PaddingWaste { severity, .. } => severity,
49 Finding::FalseSharing { severity, .. } => severity,
50 Finding::ReorderSuggestion { severity, .. } => severity,
51 Finding::LocalityIssue { severity, .. } => severity,
52 }
53 }
54
55 pub fn struct_name(&self) -> &str {
56 match self {
57 Finding::PaddingWaste { struct_name, .. } => struct_name,
58 Finding::FalseSharing { struct_name, .. } => struct_name,
59 Finding::ReorderSuggestion { struct_name, .. } => struct_name,
60 Finding::LocalityIssue { struct_name, .. } => struct_name,
61 }
62 }
63}
64
65#[derive(Debug, serde::Serialize)]
66pub struct StructReport {
67 pub struct_name: String,
68 pub source_file: Option<String>,
69 pub source_line: Option<u32>,
70 pub total_size: usize,
71 pub wasted_bytes: usize,
72 pub score: f64,
73 pub findings: Vec<Finding>,
74}
75
76#[derive(Debug, serde::Serialize)]
77pub struct Report {
78 pub structs: Vec<StructReport>,
79 pub total_structs: usize,
80 pub total_wasted_bytes: usize,
81}
82
83impl Report {
84 pub fn from_layouts(layouts: &[StructLayout]) -> Report {
86 let structs: Vec<StructReport> = layouts.iter().map(analyze_one).collect();
87 let total_wasted_bytes = structs.iter().map(|s| s.wasted_bytes).sum();
88 Report {
89 total_structs: structs.len(),
90 total_wasted_bytes,
91 structs,
92 }
93 }
94}
95
96fn analyze_one(layout: &StructLayout) -> StructReport {
97 let mut findings = Vec::new();
98
99 let gaps = padding::find_padding(layout);
101 let wasted: usize = gaps.iter().map(|g| g.bytes).sum();
102 if wasted > 0 {
104 let waste_pct = wasted as f64 / layout.total_size as f64 * 100.0;
105 let severity = if waste_pct >= 30.0 {
106 Severity::High
107 } else if waste_pct >= 10.0 {
108 Severity::Medium
109 } else {
110 Severity::Low
111 };
112 findings.push(Finding::PaddingWaste {
113 struct_name: layout.name.clone(),
114 total_size: layout.total_size,
115 wasted_bytes: wasted,
116 waste_pct,
117 gaps,
118 severity,
119 });
120 }
121
122 let (optimized_size, savings) = reorder::reorder_savings(layout);
125 if savings > 0 && !layout.is_packed && !layout.is_union {
126 let suggested_order = reorder::optimal_order(layout)
127 .iter()
128 .map(|f| f.name.clone())
129 .collect();
130 findings.push(Finding::ReorderSuggestion {
131 struct_name: layout.name.clone(),
132 original_size: layout.total_size,
133 optimized_size,
134 savings,
135 suggested_order,
136 severity: if savings >= 8 {
137 Severity::High
138 } else {
139 Severity::Medium
140 },
141 });
142 }
143
144 if !layout.is_union && false_sharing::has_false_sharing(layout) {
147 let conflicts = false_sharing::find_sharing_conflicts(layout);
148 findings.push(Finding::FalseSharing {
149 struct_name: layout.name.clone(),
150 conflicts,
151 severity: Severity::High,
152 });
153 }
154
155 if locality::has_locality_issue(layout) {
157 let (hot, cold) = locality::partition_hot_cold(layout);
158 findings.push(Finding::LocalityIssue {
159 struct_name: layout.name.clone(),
160 hot_fields: hot,
161 cold_fields: cold,
162 severity: Severity::Medium,
163 });
164 }
165
166 let score = scorer::score(layout);
167
168 StructReport {
169 struct_name: layout.name.clone(),
170 source_file: layout.source_file.clone(),
171 source_line: layout.source_line,
172 total_size: layout.total_size,
173 wasted_bytes: wasted,
174 score,
175 findings,
176 }
177}
178
179#[cfg(test)]
182mod tests {
183 use super::*;
184 use crate::ir::test_fixtures::{connection_layout, packed_layout};
185
186 #[test]
187 fn report_from_misaligned_has_padding_finding() {
188 let report = Report::from_layouts(&[connection_layout()]);
189 assert_eq!(report.total_structs, 1);
190 let sr = &report.structs[0];
191 assert!(sr.wasted_bytes > 0);
192 assert!(sr
193 .findings
194 .iter()
195 .any(|f| matches!(f, Finding::PaddingWaste { .. })));
196 }
197
198 #[test]
199 fn report_from_packed_has_no_padding_finding() {
200 let report = Report::from_layouts(&[packed_layout()]);
201 let sr = &report.structs[0];
202 assert_eq!(sr.wasted_bytes, 0);
203 assert!(!sr
204 .findings
205 .iter()
206 .any(|f| matches!(f, Finding::PaddingWaste { .. })));
207 }
208
209 #[test]
210 fn report_from_misaligned_has_reorder_suggestion() {
211 let report = Report::from_layouts(&[connection_layout()]);
212 let sr = &report.structs[0];
213 assert!(sr
214 .findings
215 .iter()
216 .any(|f| matches!(f, Finding::ReorderSuggestion { .. })));
217 }
218
219 #[test]
220 fn severity_high_when_waste_over_30_pct() {
221 let report = Report::from_layouts(&[connection_layout()]);
222 let sr = &report.structs[0];
223 let padding_finding = sr
225 .findings
226 .iter()
227 .find(|f| matches!(f, Finding::PaddingWaste { .. }))
228 .unwrap();
229 assert_eq!(padding_finding.severity(), &Severity::High);
230 }
231
232 #[test]
233 fn total_wasted_bytes_sums_across_structs() {
234 let report = Report::from_layouts(&[connection_layout(), packed_layout()]);
235 assert_eq!(report.total_structs, 2);
236 assert_eq!(report.total_wasted_bytes, 10); }
238}