vyre 0.4.0

GPU compute intermediate representation with a standard operation library
Documentation
//! Frozen backend extension contract.
//!
//! Vyre treats GPU compute as a target-agnostic intermediate representation.
//! This module defines the narrow interface that every backend — WGSL, Metal,
//! CUDA, or the pure-Rust reference interpreter — must implement. Frontends
//! emit `Program` values without knowing which backend will execute them, and
//! backends compete on implementation quality without negotiating API changes.
//! The trait signature is frozen under the five-year stability contract from
//! `ARCHITECTURE.md`.

use crate::Program;

/// Immutable execution policy supplied by the caller before dispatch.
///
/// `DispatchConfig` is an additive, non-exhaustive struct so that new backend
/// options (conformance profiles, adapter hints, etc.) can be added without
/// breaking the frozen `VyreBackend::dispatch` signature. Backends must treat
/// every field as read-only policy and must not assume the presence of any
/// particular option.
///
/// # Examples
///
/// ```
/// use vyre::DispatchConfig;
///
/// let config = DispatchConfig {
///     profile: Some("stress".to_string()),
/// };
/// ```
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub struct DispatchConfig {
    /// Optional stable profile identifier such as `default`, `stress`, or a
    /// backend-defined conformance mode.
    pub profile: Option<String>,
}

/// Actionable backend dispatch failure.
///
/// Every error that flows through the frozen `VyreBackend` contract must
/// include remediation text beginning with `Fix: `. This guarantees that
/// conform reports are directly actionable for backend authors and that
/// consumers never receive an opaque failure string.
///
/// # Examples
///
/// ```
/// use vyre::BackendError;
///
/// let err = BackendError::new("adapter not found. Fix: install a Vulkan-compatible driver.");
/// assert!(err.message.contains("Fix:"));
/// ```
#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
#[error("{message}")]
#[non_exhaustive]
pub struct BackendError {
    /// Human-readable failure text including an actionable `Fix: ` hint.
    pub message: String,
}

impl BackendError {
    /// Builds a backend error after verifying that the remedy is actionable.
    ///
    /// If the supplied message already contains a `Fix: ` section, it is used
    /// verbatim. Otherwise a generic fallback hint is appended. Callers that
    /// already have a lower-level error should include it before the fix
    /// guidance rather than dropping context.
    ///
    /// # Examples
    ///
    /// ```
    /// use vyre::BackendError;
    ///
    /// let err = BackendError::new("queue full. Fix: retry with a smaller dispatch size.");
    /// assert_eq!(err.message, "queue full. Fix: retry with a smaller dispatch size.");
    /// ```
    pub fn new(message: impl Into<String>) -> Self {
        let message = message.into();
        if message.contains("Fix: ") {
            return Self { message };
        }
        Self {
            message: format!("{message}. Fix: include backend-specific recovery guidance."),
        }
    }
}

/// The frozen contract between vyre and every execution backend.
///
/// A backend is a pure function from a validated `Program` and input buffers
/// to output buffers. Implementations must be `Send + Sync`, deterministic
/// for identical inputs, and byte-identical to the CPU reference on success.
/// This trait is the keystone of the vyre abstraction thesis: frontends do
/// not know which backend runs their IR, and backends do not know which
/// frontend produced it.
///
/// # Examples
///
/// ```
/// use vyre::{Program, VyreBackend, DispatchConfig, BackendError};
///
/// struct NullBackend;
///
/// impl VyreBackend for NullBackend {
///     fn id(&self) -> &'static str {
///         "null"
///     }
///
///     fn dispatch(
///         &self,
///         _program: &Program,
///         _inputs: &[Vec<u8>],
///         _config: &DispatchConfig,
///     ) -> Result<Vec<Vec<u8>>, BackendError> {
///         Ok(vec![])
///     }
/// }
/// ```
pub trait VyreBackend: Send + Sync {
    /// Stable backend identifier used for logging, certificates, and adapter selection.
    ///
    /// The identifier must be unique among all backends linked into the
    /// current process. Conformance reports include this string so that
    /// consumers know exactly which implementation was certified.
    ///
    /// # Examples
    ///
    /// ```
    /// use vyre::{VyreBackend, DispatchConfig, BackendError, Program};
    ///
    /// struct Stub;
    /// impl VyreBackend for Stub {
    ///     fn id(&self) -> &'static str { "stub" }
    ///     fn dispatch(&self, _: &Program, _: &[Vec<u8>], _: &DispatchConfig) -> Result<Vec<Vec<u8>>, BackendError> {
    ///         Ok(vec![])
    ///     }
    /// }
    ///
    /// assert_eq!(Stub.id(), "stub");
    /// ```
    fn id(&self) -> &'static str;

    /// Executes the program with the given input buffers and returns the output buffers.
    ///
    /// On success the returned bytes must match the pure-Rust reference
    /// implementation bit-for-bit. On failure the backend must return a
    /// [`BackendError`] whose message contains an actionable `Fix: ` hint.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use vyre::{Program, VyreBackend, DispatchConfig};
    ///
    /// # fn example(backend: &dyn VyreBackend, program: &Program) -> Result<Vec<Vec<u8>>, vyre::BackendError> {
    /// let inputs = vec![vec![1u8, 2, 3]];
    /// let config = DispatchConfig::default();
    /// backend.dispatch(program, &inputs, &config)
    /// # }
    /// ```
    ///
    /// # Errors
    ///
    /// Returns [`BackendError`] when the backend cannot complete dispatch.
    /// The error message always includes a `Fix: ` remediation section.
    fn dispatch(
        &self,
        program: &Program,
        inputs: &[Vec<u8>],
        config: &DispatchConfig,
    ) -> Result<Vec<Vec<u8>>, BackendError>;
}