1use crate::types::{MemberLayout, StructLayout};
4use serde::Serialize;
5use std::collections::HashSet;
6
7#[derive(Debug, Clone, Serialize)]
9pub struct OptimizedLayout {
10 pub name: String,
11 pub original_size: u64,
12 pub optimized_size: u64,
13 pub savings_bytes: u64,
14 pub savings_percent: f64,
15 pub struct_alignment: u64,
16 pub original_members: Vec<OptimizedMember>,
17 pub optimized_members: Vec<OptimizedMember>,
18 #[serde(skip_serializing_if = "Vec::is_empty")]
20 pub skipped_members: Vec<String>,
21 pub has_bitfields: bool,
23}
24
25#[derive(Debug, Clone, Serialize)]
27pub struct OptimizedMember {
28 pub name: String,
29 pub type_name: String,
30 pub offset: u64,
31 pub size: u64,
32 pub alignment: u64,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub bit_offset: Option<u64>,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub bit_size: Option<u64>,
37}
38
39pub fn infer_alignment(size: u64, max_align: u64) -> u64 {
42 if size == 0 {
43 return 1;
44 }
45 let natural_align = size.next_power_of_two();
47 natural_align.min(max_align).max(1)
48}
49
50fn align_up(value: u64, alignment: u64) -> u64 {
54 if alignment <= 1 {
55 return value;
56 }
57 match value.checked_add(alignment - 1) {
60 Some(sum) => (sum / alignment) * alignment,
61 None => u64::MAX,
64 }
65}
66
67#[derive(Clone)]
69struct SortableUnit {
70 members: Vec<OptimizedMember>,
71 total_size: u64,
72 alignment: u64,
73}
74
75fn find_bitfield_groups(members: &[MemberLayout]) -> Vec<Vec<usize>> {
80 let mut groups: Vec<Vec<usize>> = Vec::new();
81 let mut current_group: Vec<usize> = Vec::new();
82 let mut current_offset: Option<u64> = None;
83
84 for (idx, member) in members.iter().enumerate() {
85 if member.bit_size.is_some() {
86 let base_offset = member.offset;
87 let same_storage = match (current_offset, base_offset) {
90 (Some(curr), Some(base)) => curr == base,
91 _ => false,
92 };
93 if same_storage && !current_group.is_empty() {
94 current_group.push(idx);
96 } else {
97 if !current_group.is_empty() {
99 groups.push(std::mem::take(&mut current_group));
100 }
101 current_group.push(idx);
102 current_offset = base_offset;
103 }
104 } else if !current_group.is_empty() {
105 groups.push(std::mem::take(&mut current_group));
107 current_offset = None;
108 }
109 }
110
111 if !current_group.is_empty() {
112 groups.push(current_group);
113 }
114
115 groups
116}
117
118pub fn optimize_layout(layout: &StructLayout, max_align: u64) -> OptimizedLayout {
121 let max_align = max_align.max(1);
122 let inferred_alignment = layout
125 .members
126 .iter()
127 .filter_map(|m| m.size)
128 .filter(|&s| s > 0)
129 .map(|s| infer_alignment(s, max_align))
130 .max()
131 .unwrap_or(1);
132
133 let struct_alignment = layout.alignment.unwrap_or(inferred_alignment).min(max_align);
134
135 let bitfield_groups = find_bitfield_groups(&layout.members);
137 let bitfield_indices: HashSet<usize> = bitfield_groups.iter().flatten().copied().collect();
138 let has_bitfields = !bitfield_groups.is_empty();
139
140 let mut original_members: Vec<OptimizedMember> = Vec::new();
142 let mut skipped_members: Vec<String> = Vec::new();
143
144 for member in &layout.members {
145 let Some(size) = member.size else {
146 skipped_members.push(member.name.clone());
147 continue;
148 };
149 let Some(offset) = member.offset else {
150 skipped_members.push(member.name.clone());
151 continue;
152 };
153 if size == 0 {
155 skipped_members.push(format!("{} (zero-size)", member.name));
156 continue;
157 }
158
159 let alignment = infer_alignment(size, max_align);
160
161 original_members.push(OptimizedMember {
162 name: member.name.clone(),
163 type_name: member.type_name.clone(),
164 offset,
165 size,
166 alignment,
167 bit_offset: member.bit_offset,
168 bit_size: member.bit_size,
169 });
170 }
171
172 let mut units: Vec<SortableUnit> = Vec::new();
174 let mut processed_indices: HashSet<usize> = HashSet::new();
175
176 let mut converted_bitfield_indices: HashSet<usize> = HashSet::new();
179
180 for group in &bitfield_groups {
182 if group.is_empty() {
183 continue;
184 }
185
186 let mut group_converted: Vec<usize> = Vec::new();
188 let group_members: Vec<OptimizedMember> = group
189 .iter()
190 .filter_map(|&idx| {
191 layout.members.get(idx).and_then(|lm| {
192 original_members.iter().find(|m| m.name == lm.name).cloned().inspect(|_| {
193 group_converted.push(idx);
194 })
195 })
196 })
197 .collect();
198
199 if group_members.is_empty() {
200 continue;
201 }
202
203 let Some(total_size) = group_members.first().map(|m| m.size) else {
206 continue;
207 };
208 let alignment = group_members.iter().map(|m| m.alignment).max().unwrap_or(1);
209
210 for idx in &group_converted {
212 processed_indices.insert(*idx);
213 converted_bitfield_indices.insert(*idx);
214 }
215
216 units.push(SortableUnit { members: group_members, total_size, alignment });
217 }
218
219 let missing_bitfield_names: Vec<String> = {
223 let skipped_names: HashSet<&str> = skipped_members.iter().map(|s| s.as_str()).collect();
224 bitfield_indices
225 .iter()
226 .filter(|&&idx| !converted_bitfield_indices.contains(&idx))
227 .filter_map(|&idx| layout.members.get(idx))
228 .filter(|member| {
229 !skipped_names.contains(member.name.as_str())
231 && !skipped_members.iter().any(|s| s.starts_with(&member.name))
232 })
233 .map(|member| format!("{} (bitfield, missing info)", member.name))
234 .collect()
235 };
236 skipped_members.extend(missing_bitfield_names);
237
238 for (idx, member) in layout.members.iter().enumerate() {
240 if processed_indices.contains(&idx) || bitfield_indices.contains(&idx) {
241 continue;
242 }
243
244 if let Some(opt_member) = original_members.iter().find(|m| m.name == member.name) {
245 units.push(SortableUnit {
246 members: vec![opt_member.clone()],
247 total_size: opt_member.size,
248 alignment: opt_member.alignment,
249 });
250 }
251 }
252
253 units.sort_by(|a, b| {
255 b.alignment.cmp(&a.alignment).then_with(|| b.total_size.cmp(&a.total_size))
256 });
257
258 let mut optimized_members: Vec<OptimizedMember> = Vec::new();
260 let mut current_offset: u64 = 0;
261
262 for unit in units {
263 let aligned_offset = align_up(current_offset, unit.alignment);
265
266 for mut member in unit.members {
267 member.offset = aligned_offset;
268 if member.bit_size.is_some() {
271 member.bit_offset = None;
272 }
273 optimized_members.push(member);
274 }
275
276 current_offset = aligned_offset.saturating_add(unit.total_size);
278 }
279
280 let optimized_size = align_up(current_offset, struct_alignment);
282
283 let savings_bytes = layout.size.saturating_sub(optimized_size);
284 let savings_percent =
285 if layout.size > 0 { (savings_bytes as f64 / layout.size as f64) * 100.0 } else { 0.0 };
286
287 OptimizedLayout {
288 name: layout.name.clone(),
289 original_size: layout.size,
290 optimized_size,
291 savings_bytes,
292 savings_percent,
293 struct_alignment,
294 original_members,
295 optimized_members,
296 skipped_members,
297 has_bitfields,
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 #[test]
306 fn test_infer_alignment() {
307 assert_eq!(infer_alignment(1, 8), 1); assert_eq!(infer_alignment(2, 8), 2); assert_eq!(infer_alignment(4, 8), 4); assert_eq!(infer_alignment(8, 8), 8); assert_eq!(infer_alignment(16, 8), 8); assert_eq!(infer_alignment(3, 8), 4); assert_eq!(infer_alignment(0, 8), 1); }
315
316 #[test]
317 fn test_align_up() {
318 assert_eq!(align_up(0, 4), 0);
320 assert_eq!(align_up(1, 4), 4);
321 assert_eq!(align_up(4, 4), 4);
322 assert_eq!(align_up(5, 4), 8);
323 assert_eq!(align_up(7, 8), 8);
324
325 assert_eq!(align_up(4, 3), 6);
327 assert_eq!(align_up(6, 3), 6);
328 assert_eq!(align_up(10, 12), 12);
329 assert_eq!(align_up(24, 12), 24);
330
331 assert_eq!(align_up(10, 0), 10);
333
334 assert_eq!(align_up(5, 1), 5);
336 assert_eq!(align_up(0, 1), 0);
337
338 assert_eq!(align_up(u64::MAX - 5, 16), u64::MAX);
340 assert_eq!(align_up(u64::MAX, 8), u64::MAX);
341 }
342
343 #[test]
344 fn test_optimize_padded_struct() {
345 let mut layout = StructLayout::new("Test".to_string(), 12, Some(4));
348 layout.members = vec![
349 MemberLayout::new("a".to_string(), "char".to_string(), Some(0), Some(1)),
350 MemberLayout::new("b".to_string(), "int".to_string(), Some(4), Some(4)),
351 MemberLayout::new("c".to_string(), "char".to_string(), Some(8), Some(1)),
352 ];
353
354 let result = optimize_layout(&layout, 8);
355
356 assert_eq!(result.original_size, 12);
357 assert_eq!(result.optimized_size, 8);
358 assert_eq!(result.savings_bytes, 4);
359
360 assert_eq!(result.optimized_members[0].name, "b");
362 assert_eq!(result.optimized_members[0].offset, 0);
363 }
364
365 #[test]
366 fn test_already_optimal() {
367 let mut layout = StructLayout::new("Test".to_string(), 8, Some(4));
369 layout.members = vec![
370 MemberLayout::new("a".to_string(), "int".to_string(), Some(0), Some(4)),
371 MemberLayout::new("b".to_string(), "int".to_string(), Some(4), Some(4)),
372 ];
373
374 let result = optimize_layout(&layout, 8);
375
376 assert_eq!(result.original_size, 8);
377 assert_eq!(result.optimized_size, 8);
378 assert_eq!(result.savings_bytes, 0);
379 }
380
381 #[test]
382 fn test_skipped_members() {
383 let mut layout = StructLayout::new("Test".to_string(), 16, Some(8));
384 layout.members = vec![
385 MemberLayout::new("a".to_string(), "int".to_string(), Some(0), Some(4)),
386 MemberLayout::new("b".to_string(), "unknown".to_string(), None, None), ];
388
389 let result = optimize_layout(&layout, 8);
390
391 assert_eq!(result.skipped_members, vec!["b"]);
392 }
393
394 #[test]
395 fn test_max_align_zero_is_safely_clamped() {
396 let mut layout = StructLayout::new("Test".to_string(), 12, Some(4));
397 layout.members = vec![
398 MemberLayout::new("a".to_string(), "char".to_string(), Some(0), Some(1)),
399 MemberLayout::new("b".to_string(), "int".to_string(), Some(4), Some(4)),
400 ];
401
402 let result = optimize_layout(&layout, 0);
403 assert!(result.struct_alignment >= 1);
404 assert!(result.optimized_size > 0);
405 }
406
407 #[test]
408 fn test_bitfield_with_missing_metadata_not_lost() {
409 let mut layout = StructLayout::new("Test".to_string(), 8, Some(4));
411
412 let mut valid_bitfield =
414 MemberLayout::new("flags".to_string(), "unsigned int".to_string(), Some(0), Some(4));
415 valid_bitfield.bit_size = Some(3);
416 valid_bitfield.bit_offset = Some(0);
417
418 let mut missing_bitfield =
420 MemberLayout::new("status".to_string(), "unsigned int".to_string(), Some(0), None);
421 missing_bitfield.bit_size = Some(2);
422
423 layout.members = vec![valid_bitfield, missing_bitfield];
424
425 let result = optimize_layout(&layout, 8);
426
427 assert!(result.optimized_members.iter().any(|m| m.name == "flags"));
429
430 assert!(
432 result.skipped_members.iter().any(|s| s.contains("status")),
433 "Bitfield 'status' with missing metadata should be in skipped_members, got: {:?}",
434 result.skipped_members
435 );
436 }
437}