vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
//! MurmurHash3 32-bit specification.
//!
//! Used in bloom filters, hash tables, and distributed systems.
//! Seed fixed at 0 for deterministic conformance.

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.murmur3_32",
    layer: vyre_spec::Layer::L2,
    category: vyre_spec::MetadataCategory::A,
    version: 1,
    description: "hash murmur3_32",
    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.murmur3_32",
        input: &[],
        expected: &[0x00, 0x00, 0x00, 0x00],
        reason: "MurmurHash3 x86 32 seed 0 empty input",
    },
    GoldenSample {
        op_id: "primitive.hash.murmur3_32",
        input: b"a",
        expected: &[0xB2, 0x69, 0x25, 0x3C],
        reason: "MurmurHash3 x86 32 seed 0 of a (LE bytes of 0x3C2569B2)",
    },
    GoldenSample {
        op_id: "primitive.hash.murmur3_32",
        input: b"ab",
        expected: &[0x5F, 0xD7, 0xBF, 0x9B],
        reason: "MurmurHash3 x86 32 seed 0 of ab (LE bytes of 0x9BBFD75F)",
    },
    GoldenSample {
        op_id: "primitive.hash.murmur3_32",
        input: b"abcd",
        expected: &[0x6A, 0x67, 0xED, 0x43],
        reason: "MurmurHash3 x86 32 seed 0 of abcd",
    },
];

/// Known-answer tests for this op.
pub const KAT: &[vyre_spec::KatVector] = &[
    vyre_spec::KatVector {
        input: &[],
        expected: &[0x00, 0x00, 0x00, 0x00],
        source: "hand-verified reference vector from Rust semantics",
    },
    vyre_spec::KatVector {
        input: b"a",
        expected: &[0xB2, 0x69, 0x25, 0x3C],
        source: "LE bytes of 0x3C2569B2 (MurmurHash3 x86 32 seed 0)",
    },
    vyre_spec::KatVector {
        input: b"ab",
        expected: &[0x5F, 0xD7, 0xBF, 0x9B],
        source: "LE bytes of 0x9BBFD75F (MurmurHash3 x86 32 seed 0)",
    },
    vyre_spec::KatVector {
        input: b"abcd",
        expected: &[0x6A, 0x67, 0xED, 0x43],
        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 seed: u32 = 0;
    let len = input.len() as u32;
    let nblocks = len / 4;
    let mut h1 = seed;

    let c1: u32 = 0xcc9e_2d51;
    let c2: u32 = 0x1b87_3593;

    // Body — process 4-byte blocks
    for i in 0..nblocks as usize {
        let mut k1 = u32::from_le_bytes([
            input[i * 4],
            input[i * 4 + 1],
            input[i * 4 + 2],
            input[i * 4 + 3],
        ]);
        k1 = k1.wrapping_mul(c1);
        k1 = k1.rotate_left(15);
        k1 = k1.wrapping_mul(c2);
        h1 ^= k1;
        h1 = h1.rotate_left(13);
        h1 = h1.wrapping_mul(5).wrapping_add(0xe654_6b64);
    }

    // Tail — remaining bytes
    let tail = &input[(nblocks as usize * 4)..];
    let mut k1: u32 = 0;
    match tail.len() {
        3 => {
            k1 ^= u32::from(tail[2]) << 16;
            k1 ^= u32::from(tail[1]) << 8;
            k1 ^= u32::from(tail[0]);
            k1 = k1.wrapping_mul(c1);
            k1 = k1.rotate_left(15);
            k1 = k1.wrapping_mul(c2);
            h1 ^= k1;
        }
        2 => {
            k1 ^= u32::from(tail[1]) << 8;
            k1 ^= u32::from(tail[0]);
            k1 = k1.wrapping_mul(c1);
            k1 = k1.rotate_left(15);
            k1 = k1.wrapping_mul(c2);
            h1 ^= k1;
        }
        1 => {
            k1 ^= u32::from(tail[0]);
            k1 = k1.wrapping_mul(c1);
            k1 = k1.rotate_left(15);
            k1 = k1.wrapping_mul(c2);
            h1 ^= k1;
        }
        _ => {}
    }

    // Finalization
    h1 ^= len;
    h1 ^= h1 >> 16;
    h1 = h1.wrapping_mul(0x85eb_ca6b);
    h1 ^= h1 >> 13;
    h1 = h1.wrapping_mul(0xc2b2_ae35);
    h1 ^= h1 >> 16;

    h1.to_le_bytes().to_vec()
}

fn wgsl() -> String {
    r"
fn vyre_op(index: u32, input_len: u32) -> u32 {
    let c1: u32 = 0xCC9E2D51u;
    let c2: u32 = 0x1B873593u;
    let nblocks = input_len / 4u;
    var h1: u32 = 0u; // seed = 0

    // Body: process 4-byte blocks
    for (var i: u32 = 0u; i < nblocks; i = i + 1u) {
        var k1 = input.data[i];
        k1 = k1 * c1;
        k1 = (k1 << 15u) | (k1 >> 17u); // rotate_left(15)
        k1 = k1 * c2;
        h1 = h1 ^ k1;
        h1 = (h1 << 13u) | (h1 >> 19u); // rotate_left(13)
        h1 = h1 * 5u + 0xE6546B64u;
    }

    // Tail: remaining bytes
    let tail_start = nblocks * 4u;
    let tail_len = input_len - tail_start;
    var k1: u32 = 0u;
    if (tail_len >= 3u) {
        let b2_word = tail_start + 2u;
        k1 = k1 ^ (((input.data[b2_word / 4u] >> ((b2_word % 4u) * 8u)) & 0xFFu) << 16u);
    }
    if (tail_len >= 2u) {
        let b1_word = tail_start + 1u;
        k1 = k1 ^ (((input.data[b1_word / 4u] >> ((b1_word % 4u) * 8u)) & 0xFFu) << 8u);
    }
    if (tail_len >= 1u) {
        let b0_word = tail_start;
        k1 = k1 ^ ((input.data[b0_word / 4u] >> ((b0_word % 4u) * 8u)) & 0xFFu);
        k1 = k1 * c1;
        k1 = (k1 << 15u) | (k1 >> 17u);
        k1 = k1 * c2;
        h1 = h1 ^ k1;
    }

    // Finalization mix
    h1 = h1 ^ input_len;
    h1 = h1 ^ (h1 >> 16u);
    h1 = h1 * 0x85EBCA6Bu;
    h1 = h1 ^ (h1 >> 13u);
    h1 = h1 * 0xC2B2AE35u;
    h1 = h1 ^ (h1 >> 16u);

    return h1;
}
"
    .to_string()
}

/// Build the MurmurHash3 32-bit OpSpec.
#[inline]
pub fn vyre_op() -> OpSpec {
    OpSpec::builder("primitive.hash.murmur3_32")
        .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.murmur3_32"],
        })
        .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: "four_bytes",
                inputs: vec![1, 2, 3, 4],
            },
        ])
        .equivalence_classes(vec![
            crate::spec::types::EquivalenceClass::specific("empty input", vec![0]),
            crate::spec::types::EquivalenceClass::specific(
                "aligned 4-byte",
                vec![0x41, 0x42, 0x43, 0x44],
            ),
            crate::spec::types::EquivalenceClass::specific(
                "unaligned tail",
                vec![0x61, 0x62, 0x63],
            ),
        ])
        .expect("Fix: checked-in conform spec must satisfy the typestate builder")
}

#[cfg(test)]
mod tests {

    use super::cpu;

    #[test]
    fn murmur3_empty() {
        let result = cpu(&[]);
        let hash = u32::from_le_bytes([result[0], result[1], result[2], result[3]]);
        // MurmurHash3_x86_32("", seed=0) = 0
        assert_eq!(hash, 0);
    }
}

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