1pub use crate::arch::{ArchConfig, X86_64_SYSV};
4
5#[derive(Debug, Clone)]
7pub enum TypeInfo {
8 Primitive {
9 name: String,
10 size: usize,
11 align: usize,
12 },
13 Pointer {
14 size: usize,
15 align: usize,
16 },
17 Array {
18 element: Box<TypeInfo>,
19 count: usize,
20 size: usize,
21 align: usize,
22 },
23 Struct(Box<StructLayout>),
24 Opaque {
25 name: String,
26 size: usize,
27 align: usize,
28 },
29}
30
31impl TypeInfo {
32 pub fn size(&self) -> usize {
33 match self {
34 TypeInfo::Primitive { size, .. } => *size,
35 TypeInfo::Pointer { size, .. } => *size,
36 TypeInfo::Array { size, .. } => *size,
37 TypeInfo::Struct(l) => l.total_size,
38 TypeInfo::Opaque { size, .. } => *size,
39 }
40 }
41
42 pub fn align(&self) -> usize {
43 match self {
44 TypeInfo::Primitive { align, .. } => *align,
45 TypeInfo::Pointer { align, .. } => *align,
46 TypeInfo::Array { align, .. } => *align,
47 TypeInfo::Struct(l) => l.align,
48 TypeInfo::Opaque { align, .. } => *align,
49 }
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, serde::Serialize)]
54pub enum AccessPattern {
55 Unknown,
56 Concurrent {
57 guard: Option<String>,
58 is_atomic: bool,
59 },
60 ReadMostly,
61 Padding,
62}
63
64#[derive(Debug, Clone)]
65pub struct Field {
66 pub name: String,
67 pub ty: TypeInfo,
68 pub offset: usize,
69 pub size: usize,
70 pub align: usize,
71 pub source_file: Option<String>,
72 pub source_line: Option<u32>,
73 pub access: AccessPattern,
74}
75
76#[derive(Debug, Clone)]
78pub struct StructLayout {
79 pub name: String,
80 pub total_size: usize,
81 pub align: usize,
82 pub fields: Vec<Field>,
83 pub source_file: Option<String>,
84 pub source_line: Option<u32>,
85 pub arch: &'static ArchConfig,
86 pub is_packed: bool,
87 pub is_union: bool,
91}
92
93#[derive(Debug, Clone, PartialEq, serde::Serialize)]
94pub struct PaddingGap {
95 pub after_field: String,
96 pub bytes: usize,
97 pub at_offset: usize,
98}
99
100#[derive(Debug, Clone, serde::Serialize)]
101pub struct SharingConflict {
102 pub fields: Vec<String>,
103 pub cache_line: usize,
104}
105
106pub fn find_padding(layout: &StructLayout) -> Vec<PaddingGap> {
111 if layout.is_union {
112 return Vec::new();
113 }
114 let mut gaps = Vec::new();
115 for window in layout.fields.windows(2) {
116 let current = &window[0];
117 let next = &window[1];
118 let end = current.offset + current.size;
119 if next.offset > end {
120 gaps.push(PaddingGap {
121 after_field: current.name.clone(),
122 bytes: next.offset - end,
123 at_offset: end,
124 });
125 }
126 }
127 if let Some(last) = layout.fields.last() {
129 let end = last.offset + last.size;
130 if layout.total_size > end {
131 gaps.push(PaddingGap {
132 after_field: last.name.clone(),
133 bytes: layout.total_size - end,
134 at_offset: end,
135 });
136 }
137 }
138 gaps
139}
140
141pub fn optimal_order(layout: &StructLayout) -> Vec<&Field> {
143 let mut sorted: Vec<&Field> = layout.fields.iter().collect();
144 sorted.sort_by(|a, b| {
145 b.align
146 .cmp(&a.align)
147 .then(b.size.cmp(&a.size))
148 .then(a.name.cmp(&b.name))
149 });
150 sorted
151}
152
153#[cfg(any(test, feature = "test-helpers"))]
156pub mod test_fixtures {
157 use super::*;
158 use crate::arch::X86_64_SYSV;
159
160 pub fn connection_layout() -> StructLayout {
169 StructLayout {
170 name: "Connection".to_string(),
171 total_size: 24,
172 align: 8,
173 fields: vec![
174 Field {
175 name: "is_active".into(),
176 ty: TypeInfo::Primitive {
177 name: "bool".into(),
178 size: 1,
179 align: 1,
180 },
181 offset: 0,
182 size: 1,
183 align: 1,
184 source_file: None,
185 source_line: None,
186 access: AccessPattern::Unknown,
187 },
188 Field {
189 name: "timeout".into(),
190 ty: TypeInfo::Primitive {
191 name: "f64".into(),
192 size: 8,
193 align: 8,
194 },
195 offset: 8,
196 size: 8,
197 align: 8,
198 source_file: None,
199 source_line: None,
200 access: AccessPattern::Unknown,
201 },
202 Field {
203 name: "is_tls".into(),
204 ty: TypeInfo::Primitive {
205 name: "bool".into(),
206 size: 1,
207 align: 1,
208 },
209 offset: 16,
210 size: 1,
211 align: 1,
212 source_file: None,
213 source_line: None,
214 access: AccessPattern::Unknown,
215 },
216 Field {
217 name: "port".into(),
218 ty: TypeInfo::Primitive {
219 name: "i32".into(),
220 size: 4,
221 align: 4,
222 },
223 offset: 20,
224 size: 4,
225 align: 4,
226 source_file: None,
227 source_line: None,
228 access: AccessPattern::Unknown,
229 },
230 ],
231 source_file: None,
232 source_line: None,
233 arch: &X86_64_SYSV,
234 is_packed: false,
235 is_union: false,
236 }
237 }
238
239 pub fn packed_layout() -> StructLayout {
241 StructLayout {
242 name: "Packed".to_string(),
243 total_size: 8,
244 align: 4,
245 fields: vec![
246 Field {
247 name: "a".into(),
248 ty: TypeInfo::Primitive {
249 name: "i32".into(),
250 size: 4,
251 align: 4,
252 },
253 offset: 0,
254 size: 4,
255 align: 4,
256 source_file: None,
257 source_line: None,
258 access: AccessPattern::Unknown,
259 },
260 Field {
261 name: "b".into(),
262 ty: TypeInfo::Primitive {
263 name: "i16".into(),
264 size: 2,
265 align: 2,
266 },
267 offset: 4,
268 size: 2,
269 align: 2,
270 source_file: None,
271 source_line: None,
272 access: AccessPattern::Unknown,
273 },
274 Field {
275 name: "c".into(),
276 ty: TypeInfo::Primitive {
277 name: "i16".into(),
278 size: 2,
279 align: 2,
280 },
281 offset: 6,
282 size: 2,
283 align: 2,
284 source_file: None,
285 source_line: None,
286 access: AccessPattern::Unknown,
287 },
288 ],
289 source_file: None,
290 source_line: None,
291 arch: &X86_64_SYSV,
292 is_packed: false,
293 is_union: false,
294 }
295 }
296
297 #[test]
298 fn test_find_padding_connection() {
299 let layout = connection_layout();
300 let gaps = find_padding(&layout);
301 assert_eq!(
302 gaps,
303 vec![
304 PaddingGap {
305 after_field: "is_active".into(),
306 bytes: 7,
307 at_offset: 1
308 },
309 PaddingGap {
310 after_field: "is_tls".into(),
311 bytes: 3,
312 at_offset: 17
313 },
314 ]
315 );
316 }
317
318 #[test]
319 fn test_find_padding_packed() {
320 let layout = packed_layout();
321 assert!(find_padding(&layout).is_empty());
322 }
323
324 #[test]
325 fn test_optimal_order() {
326 let layout = connection_layout();
327 let order: Vec<&str> = optimal_order(&layout)
328 .iter()
329 .map(|f| f.name.as_str())
330 .collect();
331 assert_eq!(order[0], "timeout");
333 assert_eq!(order[1], "port");
334 assert!(order[2] == "is_active" || order[2] == "is_tls");
335 }
336}