vyre 0.4.0

GPU compute intermediate representation with a standard operation library
Documentation
//! Specification and CPU reference for `workgroup.state_machine`.

use crate::ir::DataType;
use crate::ops::{AlgebraicLaw, Backend, IntrinsicDescriptor, OpSpec};

pub const INPUTS: &[DataType] = &[DataType::U32, DataType::U32, DataType::U32];
pub const OUTPUTS: &[DataType] = &[DataType::U32, DataType::U32];
pub const LAWS: &[AlgebraicLaw] = &[];

pub const SPEC: OpSpec = OpSpec::intrinsic(
    "workgroup.state_machine",
    INPUTS,
    OUTPUTS,
    LAWS,
    wgsl_only,
    IntrinsicDescriptor::new(
        "workgroup_state_machine_kernel",
        "workgroup-sram-uniform-lookup",
        crate::ops::cpu_op::structured_intrinsic_cpu,
    ),
);
/// A single transition row in the state machine table.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Transition {
    /// Starting state id.
    pub state: u32,
    /// Event kind that triggers this transition.
    pub event: u32,
    /// Destination state id.
    pub next_state: u32,
    /// Action id emitted when the transition fires.
    pub action: u32,
}
/// State machine command status word shared by the CPU oracle and
/// WGSL lowering.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StateMachineStatus {
    /// Transition fired, action emitted.
    Ok = 0,
    /// No transition matched `(state, event)`.
    NoMatch = 1,
    /// Lanes disagreed on `(state, event)` — control-flow divergence.
    Divergent = 2,
}
/// Error returned by fallible state machine operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StateMachineError {
    /// No transition matches the current `(state, event)`.
    NoMatch,
    /// The caller's per-lane state/event arrays disagreed, violating
    /// the uniform-control-flow contract.
    Divergent,
    /// The transition table is empty — a structural configuration error.
    EmptyTable,
}
/// Bounded state machine used as the CPU reference for
/// `workgroup.state_machine`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkgroupStateMachine {
    transitions: Vec<Transition>,
    current: u32,
    actions: Vec<u32>,
}
impl WorkgroupStateMachine {
    /// Build a state machine from a transition table and initial state.
    ///
    /// # Errors
    ///
    /// Returns [`StateMachineError::EmptyTable`] when the transition table is
    /// empty.
    pub fn new(transitions: Vec<Transition>, initial: u32) -> Result<Self, StateMachineError> {
        Self::try_new(transitions, initial).ok_or(StateMachineError::EmptyTable)
    }

    /// Fallible constructor.
    pub fn try_new(transitions: Vec<Transition>, initial: u32) -> Option<Self> {
        if transitions.is_empty() {
            return None;
        }
        Some(Self {
            transitions,
            current: initial,
            actions: Vec::new(),
        })
    }

    /// Current state id.
    #[must_use]
    pub fn state(&self) -> u32 {
        self.current
    }

    /// Fired-action history in order.
    #[must_use]
    pub fn actions(&self) -> &[u32] {
        &self.actions
    }

    /// Feed one event (uniform across all workgroup lanes) and return
    /// the resulting status.
    ///
    /// # Errors
    ///
    /// Returns [`StateMachineError::NoMatch`] when no transition row
    /// applies to the current `(state, event)`. The state machine
    /// remains in its current state after a no-match event.
    pub fn step(&mut self, event: u32) -> Result<StateMachineStatus, StateMachineError> {
        if self.transitions.is_empty() {
            return Err(StateMachineError::EmptyTable);
        }
        for t in &self.transitions {
            if t.state == self.current && t.event == event {
                self.current = t.next_state;
                self.actions.push(t.action);
                return Ok(StateMachineStatus::Ok);
            }
        }
        Err(StateMachineError::NoMatch)
    }

    /// Feed one event per lane, verify uniform-control-flow, and step.
    ///
    /// # Errors
    ///
    /// Returns [`StateMachineError::Divergent`] when lanes disagree on
    /// the event kind, or [`StateMachineError::NoMatch`] when the
    /// agreed-upon event has no matching transition.
    pub fn step_uniform(
        &mut self,
        lane_events: &[u32],
    ) -> Result<StateMachineStatus, StateMachineError> {
        if lane_events.is_empty() {
            return Err(StateMachineError::EmptyTable);
        }
        let first = lane_events[0];
        if lane_events.iter().any(|&e| e != first) {
            return Err(StateMachineError::Divergent);
        }
        self.step(first)
    }

    /// Reset to the given state, preserving the transition table.
    pub fn reset(&mut self, state: u32) {
        self.current = state;
        self.actions.clear();
    }
}
pub fn wgsl_only(backend: &Backend) -> bool {
    matches!(backend, Backend::Wgsl)
}