1use vil_ir::core::WorkflowIR;
6use vil_types::{LayoutProfile, TransferMode};
7
8use crate::traits::{Diagnostic, ValidationPass, ValidationReport};
9
10pub struct LayoutLegalityPass;
11
12impl ValidationPass for LayoutLegalityPass {
13 fn name(&self) -> &'static str {
14 "LayoutLegalityPass"
15 }
16
17 fn run(&self, ir: &WorkflowIR) -> ValidationReport {
18 let mut report = ValidationReport::new();
19
20 for route in &ir.routes {
22 let iface_name = match ir.processes.get(&route.from_process) {
23 Some(p) => &p.interface_name,
24 None => {
25 report.push(Diagnostic::error(
26 "E-LAYOUT-01",
27 format!("Process not found: {}", route.from_process),
28 "Route Validation",
29 ));
30 continue;
31 }
32 };
33
34 let port = match ir
35 .interfaces
36 .get(iface_name)
37 .and_then(|i| i.ports.get(&route.from_port))
38 {
39 Some(p) => p,
40 None => continue,
41 };
42
43 let msg = match ir.messages.get(&port.message_name) {
44 Some(m) => m,
45 None => continue,
46 };
47
48 let is_zero_copy_transfer = matches!(
49 route.transfer_mode,
50 TransferMode::LoanWrite
51 | TransferMode::LoanRead
52 | TransferMode::PublishOffset
53 | TransferMode::ShareRead
54 );
55
56 if msg.layout == LayoutProfile::External && is_zero_copy_transfer {
57 report.push(Diagnostic::error(
58 "E-LAYOUT-02",
59 format!(
60 "Message '{}' has External layout, but route uses zero-copy transfer {:?}",
61 msg.name, route.transfer_mode
62 ),
63 format!("Route {} -> {}", route.from_process, route.to_process),
64 ));
65 }
66 }
67
68 for msg in ir.messages.values() {
70 if msg.layout == LayoutProfile::External {
71 continue; }
73
74 for field in &msg.fields {
75 if !is_type_vasi_compliant(&field.ty) {
76 report.push(Diagnostic::error(
77 "E-LAYOUT-03",
78 format!(
79 "Message '{}' field '{}' has non-VASI type '{:?}'. Only POD or VRef/VSlice allowed in shared memory.",
80 msg.name, field.name, field.ty
81 ),
82 format!("Message Definition: {}", msg.name),
83 ));
84 }
85 }
86 }
87
88 report
89 }
90}
91
92pub fn is_type_vasi_compliant(ty: &vil_ir::core::TypeRefIR) -> bool {
93 use vil_ir::core::TypeRefIR;
94 match ty {
95 TypeRefIR::Primitive(_) => true,
96 TypeRefIR::VRef(_) => true,
97 TypeRefIR::VSlice(_) => true,
98 TypeRefIR::Named(_) => true, TypeRefIR::Unknown(name) => {
100 let forbidden = ["String", "Vec", "Box", "HashMap", "Arc", "Rc"];
102 !forbidden.iter().any(|&f| name.contains(f))
103 }
104 }
105}