vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
use vyre::ir::{BufferDecl, DataType as IrDataType, Expr, Node, Program};
use vyre::Error;
use vyre_conform::enforce::category::Category;
use vyre_conform::oracles::cpu_ref::CpuReferenceOracle;
use vyre_conform::oracles::spec_table::SpecTableOracle;
use vyre_conform::oracles::{Oracle, Verdict};
use vyre_conform::reference::interp;
use vyre_conform::spec::Value;
use vyre_conform::spec::{OracleKind, SpecRow, SpecSource};
use vyre_conform::types::{DataType, OpSignature, OpSpec, Strictness};

static ROW_INPUT: &[u8] = &[1, 0, 0, 0];
static ROW_EXPECTED: &[u8] = &[2, 0, 0, 0];
static ROW_INPUTS: &[&[u8]] = &[ROW_INPUT];
static SPEC_ROWS: &[SpecRow] = &[SpecRow::new(
    ROW_INPUTS,
    ROW_EXPECTED,
    "panic isolation regression row",
    SpecSource::HandWritten,
)];

fn output_bytes(value: &Value) -> &[u8] {
    let Value::Bytes(bytes) = value else {
        panic!("reference output must be raw bytes");
    };
    bytes
}

fn panic_cpu(_input: &[u8]) -> Vec<u8> {
    panic!("synthetic cpu panic")
}

fn dummy_wgsl() -> String {
    "fn vyre_op(a: u32) -> u32 { return a; }".to_string()
}

fn panic_spec() -> OpSpec {
    OpSpec::builder("adversarial.cpu_panic")
        .signature(OpSignature {
            inputs: vec![DataType::U32],
            output: DataType::U32,
        })
        .cpu_fn(panic_cpu)
        .wgsl_fn(dummy_wgsl)
        .category(Category::unclassified())
        .laws(vec![])
        .strictness(Strictness::Strict)
        .version(1)
        .spec_table(SPEC_ROWS)
        .oracle_override(Some(OracleKind::CpuReference))
        .build()
        .expect("synthetic spec should build")
}

#[test]
fn bytes_buffer_load_and_store_are_byte_addressed_not_noops() {
    let program = Program::new(
        vec![
            BufferDecl::read("input", 0, IrDataType::Bytes),
            BufferDecl::read_write("out", 1, IrDataType::Bytes),
        ],
        [1, 1, 1],
        vec![Node::store(
            "out",
            Expr::u32(1),
            Expr::load("input", Expr::u32(2)),
        )],
    );

    let outputs = interp::run(
        &program,
        &[
            Value::Bytes(b"abcdef".to_vec()),
            Value::Bytes(b"XYZ".to_vec()),
        ],
    )
    .expect("bytes load/store should execute");

    assert_eq!(output_bytes(&outputs[0]), b"Xcdef");
}

#[test]
fn cast_negative_i32_to_u32_returns_invalid_program_instead_of_panicking() {
    let program = Program::new(
        vec![BufferDecl::read_write("out", 0, IrDataType::U32)],
        [1, 1, 1],
        vec![Node::store(
            "out",
            Expr::u32(0),
            Expr::Cast {
                target: IrDataType::U32,
                value: Box::new(Expr::i32(-1)),
            },
        )],
    );

    assert!(matches!(
        interp::run(&program, &[Value::Bytes(vec![0; 4])]),
        Err(Error::Interp { .. })
    ));
}

#[test]
fn negative_loop_bounds_return_invalid_program_instead_of_panicking() {
    let program = Program::new(
        vec![BufferDecl::read_write("out", 0, IrDataType::U32)],
        [1, 1, 1],
        vec![Node::loop_for(
            "i",
            Expr::i32(-1),
            Expr::u32(1),
            vec![Node::store("out", Expr::u32(0), Expr::u32(1))],
        )],
    );

    assert!(matches!(
        interp::run(&program, &[Value::Bytes(vec![0; 4])]),
        Err(Error::Interp { .. })
    ));
}

#[test]
fn cpu_reference_oracle_maps_cpu_panic_to_fail_verdict() {
    let verdict = CpuReferenceOracle.verify(&panic_spec(), &[1], &[0]);

    assert!(matches!(verdict, Verdict::Fail { .. }));
}

#[test]
fn spec_table_oracle_maps_cpu_panic_to_fail_verdict() {
    let verdict = SpecTableOracle.verify(
        &panic_spec(),
        &[1],
        &[u32::from_le_bytes([
            ROW_EXPECTED[0],
            ROW_EXPECTED[1],
            ROW_EXPECTED[2],
            ROW_EXPECTED[3],
        ])],
    );

    assert!(matches!(verdict, Verdict::Fail { .. }));
}