polydat_core/compile/
lattice.rs1use crate::kernel::PolydatProgram;
19
20pub struct ConeEntry {
22 pub label: String,
24 pub members: Vec<String>,
26 pub boundary_in: usize,
28 pub boundary_out: usize,
30}
31
32pub struct ResidueEntry {
34 pub name: String,
36 pub p3_classifiable: bool,
39 pub p2_capable: bool,
43}
44
45pub struct LatticeReport {
47 pub cones: Vec<ConeEntry>,
49 pub fused_nodes: usize,
51 pub residue: Vec<ResidueEntry>,
53 pub p2_headroom: usize,
57 pub p3_unfused: usize,
61}
62
63#[cfg(feature = "jit")]
64fn p3_classifiable(node: &dyn crate::ast::PolydatNode) -> bool {
65 !matches!(
66 crate::compile::jit::classify_node(node),
67 crate::compile::jit::JitOp::Fallback
68 )
69}
70
71#[cfg(not(feature = "jit"))]
72fn p3_classifiable(_node: &dyn crate::ast::PolydatNode) -> bool {
73 false
74}
75
76pub fn lattice_report(program: &PolydatProgram) -> LatticeReport {
78 let mut cones = Vec::new();
79 let mut residue = Vec::new();
80 let mut fused_nodes = 0;
81 for i in 0..program.node_count() {
82 let node = program.node_ref(i);
83 if let Some(sub) = node.fusion_subgraph() {
84 let members: Vec<String> = sub.members.iter().map(|m| m.meta().name.clone()).collect();
85 fused_nodes += members.len();
86 cones.push(ConeEntry {
87 label: node.meta().name.clone(),
88 members,
89 boundary_in: node.meta().wire_inputs().len(),
90 boundary_out: node.meta().outs.len(),
91 });
92 } else {
93 residue.push(ResidueEntry {
94 name: node.meta().name.clone(),
95 p3_classifiable: p3_classifiable(node),
96 p2_capable: node.compiled_u64().is_some(),
97 });
98 }
99 }
100 let p2_headroom = residue
101 .iter()
102 .filter(|r| r.p2_capable && !r.p3_classifiable)
103 .count();
104 let p3_unfused = residue.iter().filter(|r| r.p3_classifiable).count();
105 LatticeReport {
106 cones,
107 fused_nodes,
108 residue,
109 p2_headroom,
110 p3_unfused,
111 }
112}
113
114impl std::fmt::Display for LatticeReport {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 writeln!(
117 f,
118 "lattice: {} cone{} ({} node{} fused), {} interpreter node{}",
119 self.cones.len(),
120 if self.cones.len() == 1 { "" } else { "s" },
121 self.fused_nodes,
122 if self.fused_nodes == 1 { "" } else { "s" },
123 self.residue.len(),
124 if self.residue.len() == 1 { "" } else { "s" },
125 )?;
126 for cone in &self.cones {
127 writeln!(
128 f,
129 " cone {} — {} member{}, boundary {}→{}",
130 cone.label,
131 cone.members.len(),
132 if cone.members.len() == 1 { "" } else { "s" },
133 cone.boundary_in,
134 cone.boundary_out,
135 )?;
136 }
137 for r in &self.residue {
138 let tier = match (r.p3_classifiable, r.p2_capable) {
139 (true, _) => "p3-classifiable, unfused",
140 (false, true) => "p2-capable (headroom)",
141 (false, false) => "p1-only",
142 };
143 writeln!(f, " interp {:30} [{tier}]", r.name)?;
144 }
145 if self.p2_headroom > 0 {
146 writeln!(
147 f,
148 " headroom: {} node{} p2-capable without p3 — candidates \
149 for P2-at-cone-boundaries (SRD-105)",
150 self.p2_headroom,
151 if self.p2_headroom == 1 { "" } else { "s" },
152 )?;
153 }
154 Ok(())
155 }
156}