use crate::traits::{Diagnostic, ValidationPass, ValidationReport};
use vil_ir::core::WorkflowIR;
pub struct MemoryClassCompatibilityPass;
impl ValidationPass for MemoryClassCompatibilityPass {
fn name(&self) -> &'static str {
"MemoryClassCompatibilityPass"
}
fn run(&self, ir: &WorkflowIR) -> ValidationReport {
let mut report = ValidationReport::new();
for route in &ir.routes {
let transfer_mode = route.transfer_mode;
let msg_name = ir
.interfaces
.get(&format!("{}Interface", route.from_process))
.and_then(|iface| iface.ports.get(&route.from_port))
.map(|p| p.message_name.clone())
.or_else(|| {
ir.interfaces
.values()
.find_map(|iface| iface.ports.get(&route.from_port))
.map(|p| p.message_name.clone())
});
let Some(msg_name) = msg_name else { continue };
let Some(msg_ir) = ir.messages.get(&msg_name) else {
continue;
};
let memory_class = msg_ir.memory_class;
let allowed = memory_class.allowed_transfer_modes();
if !allowed.contains(&transfer_mode) {
report.push(Diagnostic::error(
"MCL01",
format!(
"Route '{}.{} -> {}.{}': message '{}' has memory_class '{}' \
which does not allow transfer_mode '{}'. \
Allowed modes: {}.",
route.from_process,
route.from_port,
route.to_process,
route.to_port,
msg_name,
memory_class,
transfer_mode,
allowed
.iter()
.map(|m| format!("{}", m))
.collect::<Vec<_>>()
.join(", "),
),
format!("{}/{}", route.from_process, route.from_port),
));
}
}
report
}
}
#[cfg(test)]
mod tests {
use super::*;
use vil_ir::builder::{InterfaceBuilder, MessageBuilder, ProcessBuilder, WorkflowBuilder};
use vil_types::{CleanupPolicy, LayoutProfile, MemoryClass, QueueKind, TransferMode};
fn build_simple_workflow(
msg_memory_class: MemoryClass,
route_transfer: TransferMode,
) -> WorkflowIR {
WorkflowBuilder::new("TestFlow")
.add_message(
MessageBuilder::new("Payload")
.layout(LayoutProfile::Flat)
.memory_class(msg_memory_class)
.build(),
)
.add_interface(
InterfaceBuilder::new("SenderInterface")
.out_port("out", "Payload")
.queue(QueueKind::Spsc, 8)
.done()
.build(),
)
.add_interface(
InterfaceBuilder::new("ReceiverInterface")
.in_port("in_port", "Payload")
.queue(QueueKind::Spsc, 8)
.done()
.build(),
)
.add_process(
ProcessBuilder::new("Sender", "SenderInterface")
.cleanup(CleanupPolicy::ReclaimOrphans)
.build(),
)
.add_process(
ProcessBuilder::new("Receiver", "ReceiverInterface")
.cleanup(CleanupPolicy::ReclaimOrphans)
.build(),
)
.route_ext("Sender", "out", "Receiver", "in_port", route_transfer, None)
.build()
}
#[test]
fn test_paged_exchange_loan_write_ok() {
let ir = build_simple_workflow(MemoryClass::PagedExchange, TransferMode::LoanWrite);
let report = MemoryClassCompatibilityPass.run(&ir);
assert!(
!report.has_errors(),
"PagedExchange + LoanWrite should be valid: {:?}",
report.diagnostics
);
}
#[test]
fn test_control_heap_loan_write_illegal() {
let ir = build_simple_workflow(MemoryClass::ControlHeap, TransferMode::LoanWrite);
let report = MemoryClassCompatibilityPass.run(&ir);
assert!(
report.has_errors(),
"ControlHeap + LoanWrite should raise MCL01"
);
assert!(
report.diagnostics.iter().any(|d| d.code == "MCL01"),
"Expected MCL01 diagnostic, got: {:?}",
report.diagnostics
);
}
#[test]
fn test_control_heap_copy_ok() {
let ir = build_simple_workflow(MemoryClass::ControlHeap, TransferMode::Copy);
let report = MemoryClassCompatibilityPass.run(&ir);
assert!(
!report.has_errors(),
"ControlHeap + Copy should be valid: {:?}",
report.diagnostics
);
}
#[test]
fn test_pinned_remote_copy_illegal() {
let ir = build_simple_workflow(MemoryClass::PinnedRemote, TransferMode::Copy);
let report = MemoryClassCompatibilityPass.run(&ir);
assert!(
report.has_errors(),
"PinnedRemote + Copy should raise MCL01"
);
assert!(report.diagnostics.iter().any(|d| d.code == "MCL01"));
}
#[test]
fn test_local_scratch_copy_ok() {
let ir = build_simple_workflow(MemoryClass::LocalScratch, TransferMode::Copy);
let report = MemoryClassCompatibilityPass.run(&ir);
assert!(
!report.has_errors(),
"LocalScratch + Copy should be valid: {:?}",
report.diagnostics
);
}
}