1use padlock_core::arch::ArchConfig;
8use padlock_core::ir::{Field, StructLayout, TypeInfo};
9use tree_sitter::{Node, Parser};
10
11fn zig_type_size_align(ty: &str, arch: &'static ArchConfig) -> (usize, usize) {
14 let ty = ty.trim();
15 match ty {
16 "bool" => (1, 1),
17 "u8" | "i8" => (1, 1),
18 "u16" | "i16" | "f16" => (2, 2),
19 "u32" | "i32" | "f32" => (4, 4),
20 "u64" | "i64" | "f64" => (8, 8),
21 "u128" | "i128" | "f128" => (16, 16),
22 "f80" => (10, 16),
24 "usize" | "isize" => (arch.pointer_size, arch.pointer_size),
25 "void" | "anyopaque" => (0, 1),
26 "type" | "anytype" | "comptime_int" | "comptime_float" => {
28 (arch.pointer_size, arch.pointer_size)
29 }
30
31 "c_char" | "c_uchar" | "c_schar" => (1, 1),
33 "c_short" | "c_ushort" => (2, 2),
34 "c_int" | "c_uint" => (4, 4),
35 "c_long" | "c_ulong" => (arch.pointer_size, arch.pointer_size), "c_longlong" | "c_ulonglong" => (8, 8),
37 "c_float" => (4, 4),
38 "c_double" => (8, 8),
39 "c_longdouble" => (16, 16),
40
41 ty if (ty.starts_with('u') || ty.starts_with('i'))
47 && ty[1..].bytes().all(|b| b.is_ascii_digit())
48 && !ty[1..].is_empty() =>
49 {
50 if let Ok(bits) = ty[1..].parse::<usize>() {
51 let bytes = bits.div_ceil(8).max(1);
52 let align = bytes.next_power_of_two().min(8);
54 (bytes, align)
55 } else {
56 (arch.pointer_size, arch.pointer_size)
57 }
58 }
59
60 _ => (arch.pointer_size, arch.pointer_size),
61 }
62}
63
64fn type_node_size_align(source: &str, node: Node<'_>, arch: &'static ArchConfig) -> (usize, usize) {
66 match node.kind() {
67 "builtin_type" | "identifier" => {
68 let text = source[node.byte_range()].trim();
69 zig_type_size_align(text, arch)
70 }
71 "pointer_type" => (arch.pointer_size, arch.pointer_size),
73 "nullable_type" => {
76 if let Some(inner) = find_child_by_kinds(node, &["pointer_type"]) {
78 let _ = inner; (arch.pointer_size, arch.pointer_size)
80 } else if let Some(inner) = find_first_type_child(source, node) {
81 let (sz, al) = type_node_size_align(source, inner, arch);
82 let tagged = (sz + 1).next_multiple_of(al.max(1));
84 (tagged, al.max(1))
85 } else {
86 (arch.pointer_size, arch.pointer_size)
87 }
88 }
89 "slice_type" => (arch.pointer_size * 2, arch.pointer_size),
91 "array_type" => {
93 if let Some((count, elem_sz, elem_al)) = parse_array_type(source, node, arch) {
94 (elem_sz * count, elem_al)
95 } else {
96 (arch.pointer_size, arch.pointer_size)
97 }
98 }
99 "error_union" => (arch.pointer_size * 2, arch.pointer_size),
101 _ => (arch.pointer_size, arch.pointer_size),
102 }
103}
104
105fn parse_array_type(
107 source: &str,
108 node: Node<'_>,
109 arch: &'static ArchConfig,
110) -> Option<(usize, usize, usize)> {
111 let mut count: Option<usize> = None;
113 let mut elem: Option<(usize, usize)> = None;
114
115 for i in 0..node.child_count() {
116 let child = node.child(i)?;
117 match child.kind() {
118 "integer" | "integer_literal" => {
119 let text = source[child.byte_range()].trim();
120 count = text.parse::<usize>().ok();
121 }
122 "builtin_type" | "identifier" | "pointer_type" | "slice_type" | "array_type"
123 | "nullable_type" => {
124 elem = Some(type_node_size_align(source, child, arch));
125 }
126 _ => {}
127 }
128 }
129
130 let count = count?;
131 let (esz, eal) = elem.unwrap_or((arch.pointer_size, arch.pointer_size));
132 Some((count, esz, eal))
133}
134
135fn find_child_by_kinds<'a>(node: Node<'a>, kinds: &[&str]) -> Option<Node<'a>> {
136 for i in 0..node.child_count() {
137 if let Some(c) = node.child(i)
138 && kinds.contains(&c.kind())
139 {
140 return Some(c);
141 }
142 }
143 None
144}
145
146fn find_first_type_child<'a>(source: &str, node: Node<'a>) -> Option<Node<'a>> {
147 let _ = source;
148 for i in 0..node.child_count() {
149 if let Some(c) = node.child(i) {
150 match c.kind() {
151 "builtin_type" | "identifier" | "pointer_type" | "slice_type" | "array_type"
152 | "nullable_type" | "error_union" => return Some(c),
153 _ => {}
154 }
155 }
156 }
157 None
158}
159
160fn extract_structs(source: &str, root: Node<'_>, arch: &'static ArchConfig) -> Vec<StructLayout> {
163 let mut layouts = Vec::new();
164 let mut stack = vec![root];
165
166 while let Some(node) = stack.pop() {
167 for i in (0..node.child_count()).rev() {
168 if let Some(c) = node.child(i) {
169 stack.push(c);
170 }
171 }
172
173 if node.kind() == "variable_declaration"
174 && let Some(layout) = parse_variable_declaration(source, node, arch)
175 {
176 layouts.push(layout);
177 }
178 }
179 layouts
180}
181
182fn parse_variable_declaration(
183 source: &str,
184 node: Node<'_>,
185 arch: &'static ArchConfig,
186) -> Option<StructLayout> {
187 let source_line = node.start_position().row as u32 + 1;
188 let mut name: Option<String> = None;
189 let mut struct_node: Option<Node> = None;
190 let mut union_node: Option<Node> = None;
191
192 for i in 0..node.child_count() {
193 let child = node.child(i)?;
194 match child.kind() {
195 "identifier" => {
196 if name.is_none() {
198 name = Some(source[child.byte_range()].to_string());
199 }
200 }
201 "struct_declaration" => struct_node = Some(child),
202 "union_declaration" => union_node = Some(child),
203 _ => {}
204 }
205 }
206
207 let name = name?;
208 if let Some(sn) = struct_node {
209 parse_struct_declaration(source, sn, name, arch, source_line)
210 } else if let Some(un) = union_node {
211 parse_union_declaration(source, un, name, arch, source_line)
212 } else {
213 None
214 }
215}
216
217fn parse_union_declaration(
225 source: &str,
226 node: Node<'_>,
227 name: String,
228 arch: &'static ArchConfig,
229 source_line: u32,
230) -> Option<StructLayout> {
231 let mut is_tagged = false;
232 let mut raw_fields: Vec<(String, String, usize, usize)> = Vec::new();
233
234 for i in 0..node.child_count() {
235 let child = node.child(i)?;
236 match child.kind() {
237 "enum" => is_tagged = true,
239 "container_field" => {
243 if let Some(f) = parse_container_field(source, child, arch, false) {
244 raw_fields.push(f);
245 }
246 }
247 _ => {}
248 }
249 }
250
251 if raw_fields.is_empty() {
252 return None;
253 }
254
255 let max_size = raw_fields
257 .iter()
258 .map(|(_, _, sz, _)| *sz)
259 .max()
260 .unwrap_or(0);
261 let max_align = raw_fields
262 .iter()
263 .map(|(_, _, _, al)| *al)
264 .max()
265 .unwrap_or(1);
266 let total_size = if max_align > 0 {
267 max_size.next_multiple_of(max_align)
268 } else {
269 max_size
270 };
271
272 let mut fields: Vec<Field> = raw_fields
273 .into_iter()
274 .map(|(fname, type_text, size, align)| Field {
275 name: fname,
276 ty: TypeInfo::Primitive {
277 name: type_text,
278 size,
279 align,
280 },
281 offset: 0,
282 size,
283 align,
284 source_file: None,
285 source_line: None,
286 access: padlock_core::ir::AccessPattern::Unknown,
287 })
288 .collect();
289
290 if is_tagged {
293 let n = fields.len();
294 let tag_size: usize = if n <= 256 {
295 1
296 } else if n <= 65536 {
297 2
298 } else {
299 4
300 };
301 fields.push(Field {
302 name: "__tag".to_string(),
303 ty: TypeInfo::Primitive {
304 name: format!("u{}", tag_size * 8),
305 size: tag_size,
306 align: tag_size,
307 },
308 offset: total_size, size: tag_size,
310 align: tag_size,
311 source_file: None,
312 source_line: None,
313 access: padlock_core::ir::AccessPattern::Unknown,
314 });
315 }
316
317 let struct_align = max_align; let final_size = if is_tagged {
320 let tag_size = fields.last().map(|f| f.size).unwrap_or(0);
321 (total_size + tag_size).next_multiple_of(struct_align.max(1))
322 } else {
323 total_size
324 };
325
326 Some(StructLayout {
327 name,
328 total_size: final_size,
329 align: struct_align,
330 fields,
331 source_file: None,
332 source_line: Some(source_line),
333 arch,
334 is_packed: false,
335 is_union: true,
336 is_repr_rust: false,
337 })
338}
339
340fn parse_struct_declaration(
341 source: &str,
342 node: Node<'_>,
343 name: String,
344 arch: &'static ArchConfig,
345 source_line: u32,
346) -> Option<StructLayout> {
347 let mut is_packed = false;
348 let mut is_extern = false;
349 let mut raw_fields: Vec<(String, String, usize, usize)> = Vec::new();
351
352 for i in 0..node.child_count() {
353 let child = node.child(i)?;
354 match child.kind() {
355 "packed" => is_packed = true,
356 "extern" => is_extern = true,
357 "container_field" => {
358 if let Some(f) = parse_container_field(source, child, arch, is_packed) {
359 raw_fields.push(f);
360 }
361 }
362 _ => {}
363 }
364 }
365
366 if raw_fields.is_empty() {
367 return None;
368 }
369
370 let mut offset = 0usize;
375 let mut struct_align = 1usize;
376 let mut fields: Vec<Field> = Vec::new();
377
378 for (fname, type_text, size, align) in raw_fields {
379 let eff_align = if is_packed { 1 } else { align };
380 if eff_align > 0 {
381 offset = offset.next_multiple_of(eff_align);
382 }
383 struct_align = struct_align.max(eff_align);
384 fields.push(Field {
385 name: fname,
386 ty: TypeInfo::Primitive {
387 name: type_text,
388 size,
389 align,
390 },
391 offset,
392 size,
393 align: eff_align,
394 source_file: None,
395 source_line: None,
396 access: padlock_core::ir::AccessPattern::Unknown,
397 });
398 offset += size;
399 }
400
401 if !is_packed && struct_align > 0 {
402 offset = offset.next_multiple_of(struct_align);
403 }
404
405 let _ = is_extern; Some(StructLayout {
408 name,
409 total_size: offset,
410 align: struct_align,
411 fields,
412 source_file: None,
413 source_line: Some(source_line),
414 arch,
415 is_packed,
416 is_union: false,
417 is_repr_rust: false,
418 })
419}
420
421fn parse_container_field(
423 source: &str,
424 node: Node<'_>,
425 arch: &'static ArchConfig,
426 is_packed: bool,
427) -> Option<(String, String, usize, usize)> {
428 let mut field_name: Option<String> = None;
429 let mut type_text: Option<String> = None;
430 let mut size_align: Option<(usize, usize)> = None;
431
432 for i in 0..node.child_count() {
433 let child = node.child(i)?;
434 match child.kind() {
435 "identifier" if field_name.is_none() => {
436 field_name = Some(source[child.byte_range()].to_string());
437 }
438 "builtin_type" | "pointer_type" | "nullable_type" | "slice_type" | "array_type"
439 | "error_union" => {
440 let text = source[child.byte_range()].to_string();
441 size_align = Some(type_node_size_align(source, child, arch));
442 type_text = Some(text);
443 }
444 "identifier" => {
445 let text = source[child.byte_range()].trim().to_string();
447 size_align = Some(zig_type_size_align(&text, arch));
448 type_text = Some(text);
449 }
450 _ => {}
451 }
452 }
453
454 let name = field_name.filter(|n| !n.is_empty())?;
457 let ty = type_text.unwrap_or_else(|| "anyopaque".to_string());
458 let (mut size, align) = size_align.unwrap_or((arch.pointer_size, arch.pointer_size));
459
460 if is_packed && size == 0 {
461 size = 0; }
463
464 Some((name, ty, size, align))
465}
466
467pub fn parse_zig(source: &str, arch: &'static ArchConfig) -> anyhow::Result<Vec<StructLayout>> {
470 let mut parser = Parser::new();
471 parser.set_language(&tree_sitter_zig::LANGUAGE.into())?;
472 let tree = parser
473 .parse(source, None)
474 .ok_or_else(|| anyhow::anyhow!("tree-sitter-zig parse failed"))?;
475 Ok(extract_structs(source, tree.root_node(), arch))
476}
477
478#[cfg(test)]
481mod tests {
482 use super::*;
483 use padlock_core::arch::X86_64_SYSV;
484
485 #[test]
486 fn parse_simple_zig_struct() {
487 let src = "const Point = struct { x: u32, y: u32 };";
488 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
489 assert_eq!(layouts.len(), 1);
490 assert_eq!(layouts[0].name, "Point");
491 assert_eq!(layouts[0].fields.len(), 2);
492 assert_eq!(layouts[0].total_size, 8);
493 }
494
495 #[test]
496 fn zig_layout_with_padding() {
497 let src = "const T = struct { a: bool, b: u64 };";
498 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
499 assert_eq!(layouts.len(), 1);
500 let l = &layouts[0];
501 assert_eq!(l.fields[0].offset, 0); assert_eq!(l.fields[1].offset, 8); assert_eq!(l.total_size, 16);
504 }
505
506 #[test]
507 fn zig_packed_struct_no_padding() {
508 let src = "const Packed = packed struct { a: u8, b: u32 };";
509 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
510 assert_eq!(layouts.len(), 1);
511 let l = &layouts[0];
512 assert!(l.is_packed);
513 assert_eq!(l.fields[0].offset, 0);
514 assert_eq!(l.fields[1].offset, 1); assert_eq!(l.total_size, 5);
516 }
517
518 #[test]
519 fn zig_extern_struct_detected() {
520 let src = "const Extern = extern struct { x: i32, y: f64 };";
521 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
522 assert_eq!(layouts.len(), 1);
523 let l = &layouts[0];
524 assert_eq!(l.fields[0].offset, 0);
526 assert_eq!(l.fields[1].offset, 8);
527 assert_eq!(l.total_size, 16);
528 }
529
530 #[test]
531 fn zig_pointer_field_is_pointer_sized() {
532 let src = "const S = struct { ptr: *u8 };";
533 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
534 assert_eq!(layouts[0].fields[0].size, 8);
535 assert_eq!(layouts[0].fields[0].align, 8);
536 }
537
538 #[test]
539 fn zig_optional_pointer_is_pointer_sized() {
540 let src = "const S = struct { opt: ?*u8 };";
541 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
542 assert_eq!(layouts[0].fields[0].size, 8);
543 }
544
545 #[test]
546 fn zig_slice_is_two_words() {
547 let src = "const S = struct { buf: []u8 };";
548 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
549 assert_eq!(layouts[0].fields[0].size, 16); }
551
552 #[test]
553 fn zig_usize_follows_arch() {
554 let src = "const S = struct { n: usize };";
555 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
556 assert_eq!(layouts[0].fields[0].size, 8);
557 }
558
559 #[test]
560 fn zig_multiple_structs_parsed() {
561 let src = "const A = struct { x: u8 };\nconst B = struct { y: u64 };";
562 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
563 assert_eq!(layouts.len(), 2);
564 assert!(layouts.iter().any(|l| l.name == "A"));
565 assert!(layouts.iter().any(|l| l.name == "B"));
566 }
567
568 #[test]
569 fn zig_array_field_size() {
570 let src = "const S = struct { buf: [4]u32 };";
571 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
572 assert_eq!(layouts[0].fields[0].size, 16); }
574
575 #[test]
578 fn zig_bare_union_parsed_as_union() {
579 let src = "const U = union { a: u8, b: u32 };";
580 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
581 assert_eq!(layouts.len(), 1);
582 let l = &layouts[0];
583 assert_eq!(l.name, "U");
584 assert!(l.is_union, "union should have is_union=true");
585 }
586
587 #[test]
588 fn zig_bare_union_total_size_is_max_field() {
589 let src = "const U = union { a: u8, b: u32 };";
591 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
592 let l = &layouts[0];
593 assert_eq!(l.total_size, 4);
594 }
595
596 #[test]
597 fn zig_union_all_fields_at_offset_zero() {
598 let src = "const U = union { a: u8, b: u64 };";
599 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
600 let l = &layouts[0];
601 for field in &l.fields {
602 assert_eq!(
603 field.offset, 0,
604 "union field '{}' should be at offset 0",
605 field.name
606 );
607 }
608 }
609
610 #[test]
611 fn zig_tagged_union_has_tag_field() {
612 let src = "const T = union(enum) { ok: u32, err: void };";
613 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
614 let l = &layouts[0];
615 assert!(
616 l.fields.iter().any(|f| f.name == "__tag"),
617 "tagged union should have a synthetic __tag field"
618 );
619 }
620
621 #[test]
622 fn zig_tagged_union_size_includes_tag() {
623 let src = "const T = union(enum) { ok: u32, err: void };";
626 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
627 let l = &layouts[0];
628 assert_eq!(l.total_size, 8);
630 }
631
632 #[test]
633 fn zig_union_with_largest_field_u64() {
634 let src = "const U = union { a: u8, b: u64, c: u32 };";
636 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
637 let l = &layouts[0];
638 assert_eq!(l.total_size, 8);
639 assert_eq!(l.align, 8);
640 }
641
642 #[test]
643 fn zig_struct_and_union_in_same_file() {
644 let src = "const S = struct { x: u32 };\nconst U = union { a: u8, b: u32 };";
645 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
646 assert_eq!(layouts.len(), 2);
647 assert!(layouts.iter().any(|l| l.name == "S" && !l.is_union));
648 assert!(layouts.iter().any(|l| l.name == "U" && l.is_union));
649 }
650
651 #[test]
654 fn zig_empty_union_returns_none() {
655 let src = "const E = union {};";
657 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
658 assert!(layouts.is_empty(), "empty union should produce no layout");
659 }
660
661 #[test]
662 fn zig_union_no_padding_finding() {
663 let src = "const U = union { a: u8, b: u64 };";
665 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
666 let gaps = padlock_core::ir::find_padding(&layouts[0]);
667 assert!(
668 gaps.is_empty(),
669 "unions should have no padding gaps: {:?}",
670 gaps
671 );
672 }
673
674 #[test]
677 fn zig_c_interop_types_correct_size() {
678 assert_eq!(zig_type_size_align("c_char", &X86_64_SYSV), (1, 1));
679 assert_eq!(zig_type_size_align("c_short", &X86_64_SYSV), (2, 2));
680 assert_eq!(zig_type_size_align("c_ushort", &X86_64_SYSV), (2, 2));
681 assert_eq!(zig_type_size_align("c_int", &X86_64_SYSV), (4, 4));
682 assert_eq!(zig_type_size_align("c_uint", &X86_64_SYSV), (4, 4));
683 assert_eq!(zig_type_size_align("c_long", &X86_64_SYSV), (8, 8));
685 assert_eq!(zig_type_size_align("c_ulong", &X86_64_SYSV), (8, 8));
686 assert_eq!(zig_type_size_align("c_longlong", &X86_64_SYSV), (8, 8));
687 assert_eq!(zig_type_size_align("c_ulonglong", &X86_64_SYSV), (8, 8));
688 assert_eq!(zig_type_size_align("c_float", &X86_64_SYSV), (4, 4));
689 assert_eq!(zig_type_size_align("c_double", &X86_64_SYSV), (8, 8));
690 assert_eq!(zig_type_size_align("c_longdouble", &X86_64_SYSV), (16, 16));
691 }
692
693 #[test]
694 fn zig_arbitrary_width_integers() {
695 assert_eq!(zig_type_size_align("u1", &X86_64_SYSV), (1, 1));
697 assert_eq!(zig_type_size_align("u3", &X86_64_SYSV), (1, 1));
699 assert_eq!(zig_type_size_align("u9", &X86_64_SYSV), (2, 2));
701 assert_eq!(zig_type_size_align("u24", &X86_64_SYSV), (3, 4));
703 assert_eq!(zig_type_size_align("u48", &X86_64_SYSV), (6, 8));
705 assert_eq!(zig_type_size_align("i7", &X86_64_SYSV), (1, 1));
707 assert_eq!(zig_type_size_align("u129", &X86_64_SYSV), (17, 8));
709 }
710
711 #[test]
712 fn zig_struct_with_c_interop_types() {
713 let src = "const Header = extern struct { version: c_uint, length: c_ushort, flags: u8 };";
715 let layouts = parse_zig(src, &X86_64_SYSV).unwrap();
716 assert_eq!(layouts.len(), 1);
717 let l = &layouts[0];
718 assert_eq!(l.fields[0].size, 4); assert_eq!(l.fields[1].size, 2); assert_eq!(l.fields[2].size, 1); }
722}