weirflow 0.1.0

GPU-first dataflow analysis primitives for Vyre and Santh compiler pipelines.
Documentation
//! Pooled host error formatting for Weir cold paths.

use std::fmt::Write;

#[derive(Debug, Default)]
pub(crate) struct ErrorFormatScratch {
    pub(crate) buf: String,
}

impl ErrorFormatScratch {
    #[cold]
    pub(crate) fn finish(&mut self) -> String {
        std::mem::take(&mut self.buf)
    }
}

#[cold]
pub(crate) fn write_dispatch_byte_len_mismatch(
    scratch: &mut ErrorFormatScratch,
    name: &str,
    got: usize,
    required: usize,
) -> String {
    scratch.buf.clear();
    let _ = write!(
        scratch.buf,
        "{name} output has malformed byte length: got {got} bytes, expected exactly {required}. Fix: backend must emit the declared u32 output size and no trailing bytes."
    );
    scratch.finish()
}

#[cold]
pub(crate) fn write_scalar_byte_len_mismatch(
    scratch: &mut ErrorFormatScratch,
    name: &str,
    got: usize,
) -> String {
    scratch.buf.clear();
    let _ = write!(
        scratch.buf,
        "{name} output has malformed byte length: got {got} bytes, expected exactly 4. Fix: backend must emit the declared scalar u32 output size and no trailing bytes."
    );
    scratch.finish()
}

#[cold]
pub(crate) fn write_fixed_point_layout_mismatch(
    scratch: &mut ErrorFormatScratch,
    consumer: &str,
    resident_hash: u64,
    prepared_hash: u64,
) -> String {
    scratch.buf.clear();
    let _ = write!(
        scratch.buf,
        "{consumer} received resident graph layout hash {resident_hash} but prepared graph layout hash {prepared_hash}. Fix: keep the resident graph and sequence-window plan from the same FixedPointAnalysisPlan."
    );
    scratch.finish()
}

#[cfg(test)]
mod tests {
    use super::*;

    // ------------------------------------------------------------------
    // ErrorFormatScratch basics
    // ------------------------------------------------------------------

    #[test]
    fn scratch_default_is_empty() {
        let scratch = ErrorFormatScratch::default();
        assert!(scratch.buf.is_empty());
    }

    #[test]
    fn finish_returns_empty_string_when_empty() {
        let mut scratch = ErrorFormatScratch::default();
        let s = scratch.finish();
        assert_eq!(s, "");
    }

    #[test]
    fn finish_clears_buf() {
        let mut scratch = ErrorFormatScratch::default();
        scratch.buf.push_str("leftover");
        let _ = scratch.finish();
        assert!(scratch.buf.is_empty());
    }

    #[test]
    fn finish_moves_content() {
        let mut scratch = ErrorFormatScratch::default();
        scratch.buf.push_str("content");
        let s = scratch.finish();
        assert_eq!(s, "content");
    }

    // ------------------------------------------------------------------
    // write_dispatch_byte_len_mismatch
    // ------------------------------------------------------------------

    #[test]
    fn dispatch_byte_len_basic() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_dispatch_byte_len_mismatch(&mut scratch, "MyOp", 8, 16);
        assert!(msg.contains("MyOp"));
        assert!(msg.contains("8 bytes"));
        assert!(msg.contains("16"));
        assert!(msg.contains("malformed byte length"));
    }

    #[test]
    fn dispatch_byte_len_zero_got() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_dispatch_byte_len_mismatch(&mut scratch, "EmptyOp", 0, 4);
        assert!(msg.contains("0 bytes"));
        assert!(msg.contains("expected exactly 4"));
    }

    #[test]
    fn dispatch_byte_len_zero_required() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_dispatch_byte_len_mismatch(&mut scratch, "NoOutput", 8, 0);
        assert!(msg.contains("got 8 bytes"));
        assert!(msg.contains("expected exactly 0"));
    }

    #[test]
    fn dispatch_byte_len_large_values() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_dispatch_byte_len_mismatch(&mut scratch, "BigOp", usize::MAX, 1_000_000);
        assert!(msg.contains(&usize::MAX.to_string()));
        assert!(msg.contains("1000000"));
    }

    #[test]
    fn dispatch_byte_len_empty_name() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_dispatch_byte_len_mismatch(&mut scratch, "", 1, 2);
        assert!(msg.contains(" output has malformed byte length"));
    }

    #[test]
    fn dispatch_byte_len_long_name() {
        let mut scratch = ErrorFormatScratch::default();
        let long = "a".repeat(500);
        let msg = write_dispatch_byte_len_mismatch(&mut scratch, &long, 1, 2);
        assert!(msg.contains(&long));
        assert!(msg.contains("malformed byte length"));
    }

    #[test]
    fn dispatch_byte_len_fix_message_present() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_dispatch_byte_len_mismatch(&mut scratch, "X", 1, 2);
        assert!(msg.contains(
            "Fix: backend must emit the declared u32 output size and no trailing bytes."
        ));
    }

    #[test]
    fn dispatch_byte_len_same_got_and_required() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_dispatch_byte_len_mismatch(&mut scratch, "Same", 8, 8);
        assert!(msg.contains("got 8 bytes"));
        assert!(msg.contains("expected exactly 8"));
    }

    // ------------------------------------------------------------------
    // write_scalar_byte_len_mismatch
    // ------------------------------------------------------------------

    #[test]
    fn scalar_byte_len_basic() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_scalar_byte_len_mismatch(&mut scratch, "ScalarOp", 8);
        assert!(msg.contains("ScalarOp"));
        assert!(msg.contains("8 bytes"));
        assert!(msg.contains("expected exactly 4"));
    }

    #[test]
    fn scalar_byte_len_zero() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_scalar_byte_len_mismatch(&mut scratch, "ZeroOp", 0);
        assert!(msg.contains("0 bytes"));
    }

    #[test]
    fn scalar_byte_len_exactly_four() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_scalar_byte_len_mismatch(&mut scratch, "CorrectOp", 4);
        assert!(msg.contains("got 4 bytes"));
        assert!(msg.contains("expected exactly 4"));
    }

    #[test]
    fn scalar_byte_len_large() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_scalar_byte_len_mismatch(&mut scratch, "HugeOp", 1_000_000_000);
        assert!(msg.contains("1000000000 bytes"));
    }

    #[test]
    fn scalar_byte_len_empty_name() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_scalar_byte_len_mismatch(&mut scratch, "", 99);
        assert!(msg.contains(" output has malformed byte length"));
    }

    #[test]
    fn scalar_byte_len_fix_message_present() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_scalar_byte_len_mismatch(&mut scratch, "Y", 7);
        assert!(msg.contains(
            "Fix: backend must emit the declared scalar u32 output size and no trailing bytes."
        ));
    }

    #[test]
    fn scalar_byte_len_unicode_name() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_scalar_byte_len_mismatch(&mut scratch, "αβγ", 8);
        assert!(msg.contains("αβγ"));
    }

    // ------------------------------------------------------------------
    // write_fixed_point_layout_mismatch
    // ------------------------------------------------------------------

    #[test]
    fn layout_mismatch_basic() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_fixed_point_layout_mismatch(&mut scratch, "ConsumerA", 0x1234, 0x5678);
        assert!(msg.contains("ConsumerA"));
        assert!(msg.contains("4660")); // 0x1234 in decimal
        assert!(msg.contains("22136")); // 0x5678 in decimal
    }

    #[test]
    fn layout_mismatch_zero_hashes() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_fixed_point_layout_mismatch(&mut scratch, "ZeroConsumer", 0, 0);
        assert!(msg.contains("0"));
        assert!(msg.contains("Fix: keep the resident graph"));
    }

    #[test]
    fn layout_mismatch_max_hashes() {
        let mut scratch = ErrorFormatScratch::default();
        let msg =
            write_fixed_point_layout_mismatch(&mut scratch, "MaxConsumer", u64::MAX, u64::MAX);
        assert!(msg.contains(&u64::MAX.to_string()));
    }

    #[test]
    fn layout_mismatch_empty_consumer() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_fixed_point_layout_mismatch(&mut scratch, "", 1, 2);
        assert!(msg.contains(" received resident graph layout hash "));
    }

    #[test]
    fn layout_mismatch_fix_message_present() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_fixed_point_layout_mismatch(&mut scratch, "C", 1, 2);
        assert!(msg.contains("Fix: keep the resident graph and sequence-window plan from the same FixedPointAnalysisPlan."));
    }

    #[test]
    fn layout_mismatch_different_hashes() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_fixed_point_layout_mismatch(&mut scratch, "Diff", 42, 43);
        assert!(msg.contains("42"));
        assert!(msg.contains("43"));
    }

    // ------------------------------------------------------------------
    // Scratch reuse across multiple formatting calls
    // ------------------------------------------------------------------

    #[test]
    fn scratch_reuse_dispatch_then_scalar() {
        let mut scratch = ErrorFormatScratch::default();
        let msg1 = write_dispatch_byte_len_mismatch(&mut scratch, "Op1", 1, 2);
        let msg2 = write_scalar_byte_len_mismatch(&mut scratch, "Op2", 3);
        assert!(msg1.contains("Op1"));
        assert!(!msg1.contains("Op2"));
        assert!(msg2.contains("Op2"));
        assert!(!msg2.contains("Op1"));
    }

    #[test]
    fn scratch_reuse_all_three() {
        let mut scratch = ErrorFormatScratch::default();
        let m1 = write_dispatch_byte_len_mismatch(&mut scratch, "A", 1, 2);
        let m2 = write_scalar_byte_len_mismatch(&mut scratch, "B", 3);
        let m3 = write_fixed_point_layout_mismatch(&mut scratch, "C", 4, 5);
        assert!(m1.contains("A"));
        assert!(m2.contains("B"));
        assert!(m3.contains("C"));
        assert!(m1.contains("malformed byte length"));
        assert!(m2.contains("expected exactly 4"));
        assert!(m3.contains("layout hash"));
    }

    #[test]
    fn scratch_does_not_leak_previous_content() {
        let mut scratch = ErrorFormatScratch::default();
        scratch.buf.push_str("pollution");
        let msg = write_scalar_byte_len_mismatch(&mut scratch, "Clean", 8);
        assert!(!msg.contains("pollution"));
        assert!(msg.contains("Clean"));
    }

    // ------------------------------------------------------------------
    // Snapshot-like string assertions
    // ------------------------------------------------------------------

    #[test]
    fn dispatch_byte_len_snapshot() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_dispatch_byte_len_mismatch(&mut scratch, "TestDispatch", 12, 24);
        assert_eq!(
            msg,
            "TestDispatch output has malformed byte length: got 12 bytes, expected exactly 24. Fix: backend must emit the declared u32 output size and no trailing bytes."
        );
    }

    #[test]
    fn scalar_byte_len_snapshot() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_scalar_byte_len_mismatch(&mut scratch, "TestScalar", 7);
        assert_eq!(
            msg,
            "TestScalar output has malformed byte length: got 7 bytes, expected exactly 4. Fix: backend must emit the declared scalar u32 output size and no trailing bytes."
        );
    }

    #[test]
    fn layout_mismatch_snapshot() {
        let mut scratch = ErrorFormatScratch::default();
        let msg = write_fixed_point_layout_mismatch(&mut scratch, "TestConsumer", 12345, 67890);
        assert_eq!(
            msg,
            "TestConsumer received resident graph layout hash 12345 but prepared graph layout hash 67890. Fix: keep the resident graph and sequence-window plan from the same FixedPointAnalysisPlan."
        );
    }
}