use crate::ast::PolydatNode;
use crate::kernel::WireSource;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProvMode {
Raw,
Pull,
PushPull,
}
#[derive(Debug, Clone)]
pub struct GraphAnalysis {
pub total_nodes: usize,
pub num_inputs: usize,
pub num_outputs: usize,
pub output_cone_sizes: Vec<(String, usize)>,
pub max_cone_ratio: f64,
pub avg_cone_ratio: f64,
}
pub fn analyze_graph(
nodes: &[Box<dyn PolydatNode>],
wiring: &[Vec<WireSource>],
output_map: &HashMap<String, (usize, usize)>,
) -> GraphAnalysis {
let total_nodes = nodes.len();
let mut output_cone_sizes = Vec::new();
for (name, &(node_idx, _port)) in output_map {
let cone_size = compute_cone_size(node_idx, wiring);
output_cone_sizes.push((name.clone(), cone_size));
}
let max_cone = output_cone_sizes.iter().map(|(_, s)| *s).max().unwrap_or(0);
let avg_cone: f64 = if output_cone_sizes.is_empty() {
0.0
} else {
output_cone_sizes
.iter()
.map(|(_, s)| *s as f64)
.sum::<f64>()
/ output_cone_sizes.len() as f64
};
let max_cone_ratio = if total_nodes > 0 {
max_cone as f64 / total_nodes as f64
} else {
1.0
};
let avg_cone_ratio = if total_nodes > 0 {
avg_cone / total_nodes as f64
} else {
1.0
};
let mut max_input = 0usize;
for sources in wiring {
for s in sources {
if let WireSource::Input(idx) = s {
max_input = max_input.max(*idx + 1);
}
}
}
GraphAnalysis {
total_nodes,
num_inputs: max_input,
num_outputs: output_map.len(),
output_cone_sizes,
max_cone_ratio,
avg_cone_ratio,
}
}
fn compute_cone_size(node_idx: usize, wiring: &[Vec<WireSource>]) -> usize {
let mut visited = vec![false; wiring.len()];
let mut stack = vec![node_idx];
let mut count = 0;
while let Some(idx) = stack.pop() {
if idx >= visited.len() || visited[idx] {
continue;
}
visited[idx] = true;
count += 1;
for source in &wiring[idx] {
if let WireSource::NodeOutput(upstream, _) = source
&& !visited[*upstream]
{
stack.push(*upstream);
}
}
}
count
}
pub fn select_prov_mode(analysis: &GraphAnalysis) -> ProvMode {
if analysis.total_nodes < 15 && analysis.num_inputs <= 1 {
return ProvMode::Raw;
}
if analysis.num_inputs >= 2 {
return ProvMode::PushPull;
}
ProvMode::Pull
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Provenance {
Raw,
Push,
Pull,
PushPull,
Auto,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Engine {
Interpreter(crate::compile::cone::JitMode),
Closures(Provenance),
Native(Provenance),
}
impl Default for Engine {
fn default() -> Self {
#[cfg(feature = "jit")]
{
Engine::Native(Provenance::Auto)
}
#[cfg(not(feature = "jit"))]
{
Engine::Closures(Provenance::Auto)
}
}
}
impl std::fmt::Display for Engine {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Engine::Interpreter(crate::compile::cone::JitMode::Auto) => write!(f, "interpreter"),
Engine::Interpreter(crate::compile::cone::JitMode::Off) => {
write!(f, "interpreter (cones off)")
}
Engine::Interpreter(crate::compile::cone::JitMode::Force) => {
write!(f, "interpreter (cones forced)")
}
Engine::Closures(p) => write!(f, "closures ({p:?})"),
Engine::Native(p) => write!(f, "native ({p:?})"),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct EnginePlan {
pub native_segments: usize,
pub closure_steps: usize,
pub interpreted_nodes: usize,
}
impl std::fmt::Display for EnginePlan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut parts = Vec::new();
if self.native_segments > 0 {
parts.push(format!("{} native segment(s)", self.native_segments));
}
if self.closure_steps > 0 {
parts.push(format!("{} closure step(s)", self.closure_steps));
}
if self.interpreted_nodes > 0 {
parts.push(format!("{} interpreted node(s)", self.interpreted_nodes));
}
if parts.is_empty() {
write!(f, "nothing")
} else {
write!(f, "{}", parts.join(", "))
}
}
}
#[derive(Debug)]
pub enum KernelError {
Source(String),
Assembly(crate::compile::assembly::AssemblyError),
Refused {
engine: Engine,
reason: String,
},
}
impl std::fmt::Display for KernelError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
KernelError::Source(e) => write!(f, "{e}"),
KernelError::Assembly(e) => write!(f, "{e}"),
KernelError::Refused { engine, reason } => {
write!(f, "the {engine} engine refuses this program: {reason}")
}
}
}
}
impl std::error::Error for KernelError {}
impl From<crate::compile::assembly::AssemblyError> for KernelError {
fn from(e: crate::compile::assembly::AssemblyError) -> Self {
KernelError::Assembly(e)
}
}