vyre 0.4.0

GPU compute intermediate representation with a standard operation library
Documentation
use crate::ir::validate::{err, ValidationError};

/// Default maximum nested operation-call depth accepted by validation.
pub const DEFAULT_MAX_CALL_DEPTH: usize = 32;

/// Default maximum `If`/`Loop`/`Block` nesting accepted by validation.
pub const DEFAULT_MAX_NESTING_DEPTH: usize = 64;

/// Default maximum statement node count accepted by validation.
pub const DEFAULT_MAX_NODE_COUNT: usize = 100_000;

/// Mutable state used while checking program size and nesting limits.
#[derive(Debug, Default)]
pub struct LimitState {
    /// Number of statement nodes visited so far.
    pub node_count: usize,
    /// Whether the nesting depth error has already been reported.
    pub nesting_reported: bool,
    /// Whether the node count error has already been reported.
    pub node_count_reported: bool,
}

/// Increment `limits` and emit errors if depth or node count exceeds defaults.
#[inline]
pub fn check_limits(limits: &mut LimitState, depth: usize, errors: &mut Vec<ValidationError>) {
    limits.node_count = limits.node_count.saturating_add(1);
    if limits.node_count > DEFAULT_MAX_NODE_COUNT && !limits.node_count_reported {
        limits.node_count_reported = true;
        errors.push(err(format!(
            "V019: program has more than {DEFAULT_MAX_NODE_COUNT} statement nodes. Fix: split the program into smaller kernels or run an optimization pass before lowering."
        )));
    }
    if depth > DEFAULT_MAX_NESTING_DEPTH && !limits.nesting_reported {
        limits.nesting_reported = true;
        errors.push(err(format!(
            "V018: program nesting depth {depth} exceeds max {DEFAULT_MAX_NESTING_DEPTH}. Fix: flatten nested If/Loop/Block structures or split the program before lowering."
        )));
    }
}

/// Compute the maximum call depth reachable from `op_id`.
///
/// Returns `Ok(max_depth)` when within [`DEFAULT_MAX_CALL_DEPTH`], or
/// `Err(depth)` if the limit is exceeded.
#[inline]
pub fn max_call_depth(op_id: &str, depth: usize) -> Result<usize, usize> {
    if depth > DEFAULT_MAX_CALL_DEPTH {
        return Err(depth);
    }
    let Some(spec) = crate::ops::registry::lookup(op_id) else {
        return Ok(depth);
    };
    let Some(prog) = spec.program() else {
        return Ok(depth);
    };

    let mut max_depth = depth;
    let call_ops = crate::ir::transform::visit::collect_call_op_ids(&prog);
    for call_op in call_ops {
        match max_call_depth(&call_op, depth + 1) {
            Ok(d) => max_depth = max_depth.max(d),
            Err(e) => return Err(e),
        }
    }
    Ok(max_depth)
}