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