use crate::kernel::PolydatProgram;
pub struct ConeEntry {
pub label: String,
pub members: Vec<String>,
pub boundary_in: usize,
pub boundary_out: usize,
}
pub struct ResidueEntry {
pub name: String,
pub p3_classifiable: bool,
pub p2_capable: bool,
}
pub struct LatticeReport {
pub cones: Vec<ConeEntry>,
pub fused_nodes: usize,
pub residue: Vec<ResidueEntry>,
pub p2_headroom: usize,
pub p3_unfused: usize,
}
#[cfg(feature = "jit")]
fn p3_classifiable(node: &dyn crate::ast::PolydatNode) -> bool {
!matches!(
crate::compile::jit::classify_node(node),
crate::compile::jit::JitOp::Fallback
)
}
#[cfg(not(feature = "jit"))]
fn p3_classifiable(_node: &dyn crate::ast::PolydatNode) -> bool {
false
}
pub fn lattice_report(program: &PolydatProgram) -> LatticeReport {
let mut cones = Vec::new();
let mut residue = Vec::new();
let mut fused_nodes = 0;
for i in 0..program.node_count() {
let node = program.node_ref(i);
if let Some(sub) = node.fusion_subgraph() {
let members: Vec<String> = sub.members.iter().map(|m| m.meta().name.clone()).collect();
fused_nodes += members.len();
cones.push(ConeEntry {
label: node.meta().name.clone(),
members,
boundary_in: node.meta().wire_inputs().len(),
boundary_out: node.meta().outs.len(),
});
} else {
residue.push(ResidueEntry {
name: node.meta().name.clone(),
p3_classifiable: p3_classifiable(node),
p2_capable: node.compiled_u64().is_some(),
});
}
}
let p2_headroom = residue
.iter()
.filter(|r| r.p2_capable && !r.p3_classifiable)
.count();
let p3_unfused = residue.iter().filter(|r| r.p3_classifiable).count();
LatticeReport {
cones,
fused_nodes,
residue,
p2_headroom,
p3_unfused,
}
}
impl std::fmt::Display for LatticeReport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"lattice: {} cone{} ({} node{} fused), {} interpreter node{}",
self.cones.len(),
if self.cones.len() == 1 { "" } else { "s" },
self.fused_nodes,
if self.fused_nodes == 1 { "" } else { "s" },
self.residue.len(),
if self.residue.len() == 1 { "" } else { "s" },
)?;
for cone in &self.cones {
writeln!(
f,
" cone {} — {} member{}, boundary {}→{}",
cone.label,
cone.members.len(),
if cone.members.len() == 1 { "" } else { "s" },
cone.boundary_in,
cone.boundary_out,
)?;
}
for r in &self.residue {
let tier = match (r.p3_classifiable, r.p2_capable) {
(true, _) => "p3-classifiable, unfused",
(false, true) => "p2-capable (headroom)",
(false, false) => "p1-only",
};
writeln!(f, " interp {:30} [{tier}]", r.name)?;
}
if self.p2_headroom > 0 {
writeln!(
f,
" headroom: {} node{} p2-capable without p3 — candidates \
for P2-at-cone-boundaries (SRD-105)",
self.p2_headroom,
if self.p2_headroom == 1 { "" } else { "s" },
)?;
}
Ok(())
}
}