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