vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
//! FNV-1a 32-bit hash specification.
//!
//! Already in use by warpscan/keyhog. This is the conform spec
//! with both CPU reference and real WGSL kernel.

use crate::OpSpec;
use vyre_spec::GoldenSample;

/// Location-agnostic operation metadata.
pub const VYRE_OP_METADATA: vyre_spec::OpMetadata = vyre_spec::OpMetadata {
    id: "primitive.hash.fnv1a32",
    layer: vyre_spec::Layer::L2,
    category: vyre_spec::MetadataCategory::A,
    version: 1,
    description: "hash fnv1a32",
    signature: "(Bytes) -> U32",
    strictness: "strict",
    archetype_signature: "(Bytes) -> U32",
};

/// Golden samples for this op.
pub const GOLDEN: &[vyre_spec::GoldenSample] = &[
    GoldenSample {
        op_id: "primitive.hash.fnv1a32",
        input: &[],
        expected: &[0xC5, 0x9D, 0x1C, 0x81],
        reason: "FNV-1a 32 empty input offset basis",
    },
    GoldenSample {
        op_id: "primitive.hash.fnv1a32",
        input: b"a",
        expected: &[0x2C, 0x29, 0x0C, 0xE4],
        reason: "FNV-1a 32 of a",
    },
    GoldenSample {
        op_id: "primitive.hash.fnv1a32",
        input: b"foobar",
        expected: &[0x68, 0xF9, 0x9C, 0xBF],
        reason: "FNV-1a 32 of foobar",
    },
];

/// Known-answer tests for this op.
pub const KAT: &[vyre_spec::KatVector] = &[
    vyre_spec::KatVector {
        input: &[],
        expected: &[0xC5, 0x9D, 0x1C, 0x81],
        source: "hand-verified reference vector from Rust semantics",
    },
    vyre_spec::KatVector {
        input: b"a",
        expected: &[0x2C, 0x29, 0x0C, 0xE4],
        source: "hand-verified reference vector from Rust semantics",
    },
    vyre_spec::KatVector {
        input: b"foobar",
        expected: &[0x68, 0xF9, 0x9C, 0xBF],
        source: "hand-verified reference vector from Rust semantics",
    },
];

/// Adversarial inputs for this op.
pub const ADVERSARIAL: &[vyre_spec::AdversarialInput] = &[vyre_spec::AdversarialInput {
    input: &[],
    reason: "empty input exercises validation and boundary handling",
}];

fn cpu(input: &[u8]) -> Vec<u8> {
    let mut hash: u32 = 0x811c_9dc5; // FNV offset basis
    for &byte in input {
        hash ^= u32::from(byte);
        hash = hash.wrapping_mul(0x0100_0193); // FNV prime
    }
    hash.to_le_bytes().to_vec()
}

fn wgsl() -> String {
    r"
fn vyre_op(index: u32, input_len: u32) -> u32 {
    var hash: u32 = 0x811C9DC5u;
    for (var i: u32 = 0u; i < input_len; i = i + 1u) {
        let word_idx = i / 4u;
        let byte_idx = i % 4u;
        let byte_val = (input.data[word_idx] >> (byte_idx * 8u)) & 0xFFu;
        hash = hash ^ byte_val;
        hash = hash * 0x01000193u;
    }
    return hash;
}
"
    .to_string()
}

/// Build the FNV-1a 32-bit hash OpSpec.
#[inline]
pub fn vyre_op() -> OpSpec {
    OpSpec::builder("primitive.hash.fnv1a32")
        .signature(crate::spec::types::OpSignature {
            inputs: vec![crate::spec::types::DataType::Bytes],
            output: crate::spec::types::DataType::U32,
        })
        .cpu_fn(cpu)
        .wgsl_fn(wgsl)
        .category(crate::Category::A {
            composition_of: vec!["primitive.hash.fnv1a32"],
        })
        .laws(vec![crate::spec::law::AlgebraicLaw::Bounded {
            lo: 0,
            hi: u32::MAX,
        }])
        .overflow_contract(crate::spec::types::OverflowContract::Unchecked)
        .strictness(crate::spec::types::Strictness::Strict)
        .version(1)
        .boundary_values(vec![
            crate::spec::types::BoundaryValue {
                label: "empty",
                inputs: vec![0],
            },
            crate::spec::types::BoundaryValue {
                label: "single_zero",
                inputs: vec![0],
            },
            crate::spec::types::BoundaryValue {
                label: "single_ff",
                inputs: vec![255],
            },
            crate::spec::types::BoundaryValue {
                label: "ascii_a",
                inputs: vec![0x61],
            },
        ])
        .equivalence_classes(vec![
            crate::spec::types::EquivalenceClass::specific("empty input", vec![0]),
            crate::spec::types::EquivalenceClass::specific("ascii text", vec![0x41]),
            crate::spec::types::EquivalenceClass::specific("binary data", vec![0xFF]),
        ])
        .expect("Fix: checked-in conform spec must satisfy the typestate builder")
}

#[cfg(test)]
mod tests {

    use super::cpu;

    #[test]
    fn fnv1a32_empty() {
        let result = cpu(&[]);
        let hash = u32::from_le_bytes([result[0], result[1], result[2], result[3]]);
        assert_eq!(
            hash, 0x811c_9dc5,
            "FNV-1a 32 of empty input is the offset basis"
        );
    }

    #[test]
    fn fnv1a32_known_vector() {
        // FNV-1a 32 of "foobar" = 0xBF9CF968
        let result = cpu(b"foobar");
        let hash = u32::from_le_bytes([result[0], result[1], result[2], result[3]]);
        assert_eq!(hash, 0xBF9C_F968);
    }
}

#[cfg(test)]
mod proptests {
    #[test]
    fn coverage_artifacts_are_registered() {
        assert!(!super::KAT.is_empty());
        assert!(!super::ADVERSARIAL.is_empty());
    }
}