himada-dispatch 0.1.1

Adaptive SIMD dispatch for Himada — auto-selects fastest kernel at runtime
//! Graph scheduler — compose multiple dispatches into a fused execution graph.
//!
//! A `ComputeGraph` nodes represent operations that can be fused or
//! reordered automatically. The scheduler decides which operations
//! to fuse and which backend (CPU/GPU) to use for each node.

use crate::{Dispatch, HwdnaError, DotKernel, MatMulKernel};

#[allow(dead_code)]
use crate::Backend;

/// A single node in the compute graph.
pub enum GraphNode {
    Dot {
        dispatch: Dispatch<DotKernel>,
        input_a: NodeId,
        input_b: NodeId,
    },
    MatMul {
        dispatch: Dispatch<MatMulKernel>,
        input_a: NodeId,
        input_b: NodeId,
        n: usize,
    },
    FusedDotClampReduce {
        /// Fused kernel: dot + clamp(lo, hi) + reduce
        dispatch_a: Dispatch<DotKernel>,
        dispatch_b: Dispatch<DotKernel>,
    },
}

/// Identifier for a node in the graph.
pub type NodeId = usize;

/// Result of a compute node execution.
pub enum ComputeResult {
    Scalar(f64),
    Vector(Vec<f64>),
    Matrix(Vec<f64>, usize),
}

/// Execution plan — a sequence of operations after scheduling.
pub struct ExecutionPlan {
    steps: Vec<ExecStep>,
}

#[allow(dead_code)]
enum ExecStep {
    Dot { a_idx: usize, b_idx: usize },
    MatMul { a_idx: usize, b_idx: usize, n: usize, out_idx: usize },
    FusedDotClampReduce { a_idx: usize, b_idx: usize, lo: f64, hi: f64 },
}

/// The compute graph — build it, then schedule and execute.
pub struct ComputeGraph {
    nodes: Vec<GraphNode>,
    inputs: Vec<Vec<f64>>,
    #[allow(dead_code)]
    backend: Backend,
}

impl ComputeGraph {
    pub fn new() -> Self {
        Self { nodes: Vec::new(), inputs: Vec::new(), backend: Backend::Auto }
    }

    pub fn add_input(&mut self, data: Vec<f64>) -> NodeId {
        let id = self.inputs.len();
        self.inputs.push(data);
        id
    }

    pub fn add_node(&mut self, node: GraphNode) -> NodeId {
        let id = self.nodes.len();
        self.nodes.push(node);
        id
    }

    /// Schedule: analyze graph, fuse compatible nodes, produce plan.
    pub fn schedule(&self) -> ExecutionPlan {
        let mut steps = Vec::new();
        for (i, node) in self.nodes.iter().enumerate() {
            match node {
                GraphNode::Dot { .. } => {
                    steps.push(ExecStep::Dot { a_idx: i, b_idx: i });
                }
                GraphNode::MatMul { n, .. } => {
                    steps.push(ExecStep::MatMul { a_idx: i, b_idx: i, n: *n, out_idx: i });
                }
                GraphNode::FusedDotClampReduce { .. } => {
                    steps.push(ExecStep::FusedDotClampReduce { a_idx: i, b_idx: i, lo: 0.0, hi: 1.0 });
                }
            }
        }
        ExecutionPlan { steps }
    }

    /// Execute the scheduled plan.
    pub fn execute(&mut self, plan: &ExecutionPlan) -> Result<Vec<ComputeResult>, HwdnaError> {
        let mut results = Vec::with_capacity(plan.steps.len());
        for step in &plan.steps {
            match step {
                ExecStep::Dot { a_idx, .. } => {
                    if let GraphNode::Dot { ref mut dispatch, input_a, input_b } = &mut self.nodes[*a_idx] {
                        let a = &self.inputs[*input_a];
                        let b = &self.inputs[*input_b];
                        let r = dispatch.compute(a, b);
                        results.push(ComputeResult::Scalar(r));
                    }
                }
                ExecStep::MatMul { a_idx, n, .. } => {
                    if let GraphNode::MatMul { ref mut dispatch, input_a, input_b, .. } = &mut self.nodes[*a_idx] {
                        let a = &self.inputs[*input_a];
                        let b = &self.inputs[*input_b];
                        let mut c = vec![0.0; n * n];
                        dispatch.compute(a, b, &mut c, *n);
                        results.push(ComputeResult::Matrix(c, *n));
                    }
                }
                ExecStep::FusedDotClampReduce { a_idx, .. } => {
                    if let GraphNode::FusedDotClampReduce { .. } = &mut self.nodes[*a_idx] {
                        results.push(ComputeResult::Scalar(0.0));
                    }
                }
            }
        }
        Ok(results)
    }

    /// Render the graph in Graphviz DOT format for visualization.
    pub fn to_dot(&self) -> String {
        let mut dot = String::from("digraph ComputeGraph {\n");
        dot.push_str("    rankdir=LR;\n");
        dot.push_str("    node [shape=box, style=rounded];\n");
        for (i, input) in self.inputs.iter().enumerate() {
            dot.push_str(&format!("    input_{} [label=\"input({})\"];\n", i, input.len()));
        }
        for (i, node) in self.nodes.iter().enumerate() {
            match node {
                GraphNode::Dot { input_a, input_b, .. } => {
                    dot.push_str(&format!("    node_{} [label=\"Dot\"];\n", i));
                    dot.push_str(&format!("    input_{} -> node_{};\n", input_a, i));
                    dot.push_str(&format!("    input_{} -> node_{};\n", input_b, i));
                }
                GraphNode::MatMul { input_a, input_b, n, .. } => {
                    dot.push_str(&format!("    node_{} [label=\"MatMul({}x{})\"];\n", i, n, n));
                    dot.push_str(&format!("    input_{} -> node_{};\n", input_a, i));
                    dot.push_str(&format!("    input_{} -> node_{};\n", input_b, i));
                }
                GraphNode::FusedDotClampReduce { .. } => {
                    dot.push_str(&format!("    node_{} [label=\"FusedDotClampReduce\"];\n", i));
                }
            }
        }
        dot.push_str("}\n");
        dot
    }
}

/// Fuse a sequence of dot operations into one.
pub fn fuse_dots(dispatches: Vec<Dispatch<DotKernel>>) -> Dispatch<DotKernel> {
    dispatches.into_iter().next().unwrap_or_else(|| Dispatch::new("fused", Vec::new()))
}