vyre 0.4.0

GPU compute intermediate representation with a standard operation library
Documentation
// Stable binary IR wire format for serialized IR programs.

use crate::ir::{BufferDecl, Expr, Node, Program};

/// The `decode` module.
pub mod decode;
/// The `encode` module.
pub mod encode;
/// The `framing` module.
pub mod framing;
/// The `tags` module.
pub mod tags;

/// Maximum buffers accepted from one IR wire-format program.
///
/// I10 requires bounded allocation before validating semantics. This limit
/// rejects hostile wire blobs before allocating the buffer table.
pub const MAX_BUFFERS: usize = 16_384;

/// Maximum statement nodes accepted from any single wire-format node list.
///
/// I10 requires node vectors to be bounded before allocation; nested lists are
/// each checked against this budget as they are decoded.
pub const MAX_NODES: usize = 1_000_000;

/// Maximum call arguments accepted from one wire-format call expression.
///
/// I10 requires expression argument vectors to be bounded before allocation.
pub const MAX_ARGS: usize = 4_096;

/// Maximum UTF-8 string length accepted from the IR wire format.
///
/// I10 bounds allocation for names and operation identifiers carried by
/// attacker-controlled wire bytes.
pub const MAX_STRING_LEN: usize = 1 << 20;

/// Maximum recursive decode depth. Any `Node::If`, `Node::Loop`, `Node::Block`,
/// or nested `Expr` call tree deeper than this is rejected with a
/// `Fix:`-prefixed error. Prevents stack-overflow DoS from a hostile blob
/// that nests `Block(Block(... Block(...) ...))` a million times deep.
/// Covers audit L.1.35 (HIGH).
pub const MAX_DECODE_DEPTH: u32 = 256;

pub(crate) struct Reader<'a> {
    pub bytes: &'a [u8],
    pub pos: usize,
    /// Current recursion depth on the decode call stack. Incremented by
    /// every `node()` and `expr()` call and compared against
    /// [`MAX_DECODE_DEPTH`] before any nested decode proceeds.
    pub depth: u32,
}

impl Program {
    /// Serialize this IR program into the stable `VIR0` IR wire format.
    ///
    /// # Errors
    ///
    /// Returns [`crate::error::Error::WireFormatValidation`] when a count
    /// cannot be represented in the versioned wire format or when a public
    /// enum variant has no registered stable wire tag. The `message` field
    /// carries the actionable diagnostic prose including a `Fix:` hint.
    #[inline]
    pub fn to_wire(&self) -> Result<Vec<u8>, crate::error::Error> {
        encode::to_wire(self).map_err(wire_err)
    }

    /// Serialize this IR program into bytes.
    ///
    /// This compatibility wrapper preserves the pre-`to_wire` API name.
    ///
    /// On an encoding error, an empty vector is returned after logging the
    /// failure. Use [`Program::to_wire`] when the caller needs to handle the
    /// error explicitly.
    #[must_use]
    #[inline]
    pub fn to_bytes(&self) -> Vec<u8> {
        match self.to_wire() {
            Ok(bytes) => bytes,
            Err(e) => {
                tracing::error!(
                    error = %e,
                    "Program::to_bytes: wire encoding failed; returning empty bytes. \
                     This indicates a malformed Program; callers requiring strict \
                     encoding must use Program::to_wire directly."
                );
                Vec::new()
            }
        }
    }

    /// Deserialize an IR program from the stable `VIR0` IR wire format.
    ///
    /// # Errors
    ///
    /// Returns [`crate::error::Error::WireFormatValidation`] when the bytes
    /// are truncated, malformed, or contain an unknown enum tag.
    #[inline]
    pub fn from_wire(bytes: &[u8]) -> Result<Self, crate::error::Error> {
        decode::from_wire(bytes).map_err(wire_err)
    }

    /// Deserialize an IR program from bytes.
    ///
    /// This compatibility wrapper preserves the pre-`from_wire` API name.
    ///
    /// # Errors
    ///
    /// Returns the same actionable decode errors as [`Program::from_wire`].
    #[inline]
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::error::Error> {
        Self::from_wire(bytes)
    }
}

/// Wrap an internal wire-format error string in the typed [`crate::error::Error`]
/// so every public boundary of this module returns a structured variant
/// callers can pattern-match on.
fn wire_err(message: String) -> crate::error::Error {
    crate::error::Error::WireFormatValidation { message }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ir::{BufferAccess, BufferDecl, DataType, Program};

    #[test]
    #[inline]
    pub(crate) fn to_bytes_returns_empty_on_wire_error() {
        let long_name = "x".repeat(MAX_STRING_LEN + 1);
        let program = Program::new(
            vec![BufferDecl::storage(
                &long_name,
                0,
                BufferAccess::ReadOnly,
                DataType::U32,
            )],
            [1, 1, 1],
            vec![],
        );
        assert!(program.to_wire().is_err());
        assert!(program.to_bytes().is_empty());
    }
}