Skip to main content

vil_validate/
layout.rs

1// =============================================================================
2// vil_validate::layout — Layout Legality Pass
3// =============================================================================
4
5use 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        // 1. Check existing routes for zero-copy legality (Original check)
21        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        // 2. Deep Field Validation (Phase 1 Goal)
69        for msg in ir.messages.values() {
70            if msg.layout == LayoutProfile::External {
71                continue; // External layout allows anything
72            }
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, // We assume other named VIL messages are also checked
99        TypeRefIR::Unknown(name) => {
100            // Dangerous types known to be non-VASI
101            let forbidden = ["String", "Vec", "Box", "HashMap", "Arc", "Rc"];
102            !forbidden.iter().any(|&f| name.contains(f))
103        }
104    }
105}