Skip to main content

vyre_primitives/hash/
multi_hash.rs

1//! Fused multi-hash primitive.
2//!
3//! Computes CRC-32, FNV-1a32, and Adler-32 in one serial byte walk. This is
4//! the primitive authority for the fused checksum body; higher-tier crates may
5//! rename buffers or stamp parent op ids, but should not rebuild this loop.
6
7use std::sync::Arc;
8
9use vyre_foundation::ir::model::expr::Ident;
10use vyre_foundation::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
11
12use super::adler32::{
13    adler32, adler32_finalize_expr, adler32_initial_a_expr, adler32_initial_b_expr,
14    adler32_update_byte_nodes,
15};
16use super::crc32::{crc32, crc32_finalize_expr, crc32_initial_expr, crc32_update_byte_nodes};
17use super::fnv1a::{fnv1a32, fnv1a32_initial_expr, fnv1a32_update_byte_node};
18
19/// Stable Tier 2.5 op id for the fused CRC-32/FNV-1a32/Adler-32 walker.
20pub const MULTI_HASH_OP_ID: &str = "vyre-primitives::hash::multi_hash";
21
22/// CPU reference for the fused multi-hash contract.
23#[must_use]
24pub fn multi_hash_reference(bytes: &[u8]) -> (u32, u32, u32) {
25    (crc32(bytes), fnv1a32(bytes), adler32(bytes))
26}
27
28/// Build a Program that computes CRC-32, FNV-1a32, and Adler-32 over
29/// `input[0..n]` in a single walk.
30///
31/// `input[i]` packs one byte per u32 slot. The three results are packed into
32/// one output buffer: `out[0] = crc32`, `out[1] = fnv1a32`,
33/// `out[2] = adler32`.
34#[must_use]
35pub fn multi_hash_program(input: &str, out: &str, n: u32) -> Program {
36    Program::wrapped(
37        vec![
38            BufferDecl::storage(input, 0, BufferAccess::ReadOnly, DataType::U32).with_count(n),
39            BufferDecl::output(out, 1, DataType::U32).with_count(3),
40        ],
41        [1, 1, 1],
42        vec![Node::Region {
43            generator: Ident::from(MULTI_HASH_OP_ID),
44            source_region: None,
45            body: Arc::new(multi_hash_body(input, out, n)),
46        }],
47    )
48}
49
50fn multi_hash_body(input: &str, out: &str, n: u32) -> Vec<Node> {
51    vec![Node::if_then(
52        Expr::eq(Expr::InvocationId { axis: 0 }, Expr::u32(0)),
53        vec![
54            Node::let_bind("crc", crc32_initial_expr()),
55            Node::let_bind("fnv", fnv1a32_initial_expr()),
56            Node::let_bind("a", adler32_initial_a_expr()),
57            Node::let_bind("b", adler32_initial_b_expr()),
58            Node::loop_for("i", Expr::u32(0), Expr::u32(n), {
59                let mut nodes = vec![Node::let_bind(
60                    "byte",
61                    Expr::bitand(Expr::load(input, Expr::var("i")), Expr::u32(0xFF)),
62                )];
63                nodes.extend(crc32_update_byte_nodes("crc", "crc_bit", Expr::var("byte")));
64                nodes.push(fnv1a32_update_byte_node("fnv", Expr::var("byte")));
65                nodes.extend(adler32_update_byte_nodes("a", "b", Expr::var("byte")));
66                nodes
67            }),
68            Node::store(out, Expr::u32(0), crc32_finalize_expr(Expr::var("crc"))),
69            Node::store(out, Expr::u32(1), Expr::var("fnv")),
70            Node::store(
71                out,
72                Expr::u32(2),
73                adler32_finalize_expr(Expr::var("a"), Expr::var("b")),
74            ),
75        ],
76    )]
77}
78
79#[cfg(feature = "inventory-registry")]
80inventory::submit! {
81    vyre_foundation::operation::OperationRegistration::primitive(
82        MULTI_HASH_OP_ID,
83        || multi_hash_program("input", "out", 3),
84        Some(|| vec![vec![crate::wire::pack_bytes_as_u32_slice(b"abc")]]),
85        Some(|| vec![vec![crate::wire::pack_u32_slice(&[
86            0x3524_41c2,
87            0x1a47_e90b,
88            0x024D_0127,
89        ])]]),
90    )
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn reference_matches_constituent_hashes() {
99        assert_eq!(
100            multi_hash_reference(b"abc"),
101            (0x3524_41c2, 0x1a47_e90b, 0x024D_0127)
102        );
103    }
104
105    #[test]
106    fn standalone_program_is_single_multi_hash_region() {
107        let program = multi_hash_program("input", "out", 3);
108        let [Node::Region { generator, .. }] = program.entry() else {
109            panic!("expected one primitive multi_hash region");
110        };
111        assert_eq!(generator.as_str(), MULTI_HASH_OP_ID);
112        assert_eq!(program.buffers()[1].count(), 3);
113    }
114
115    #[test]
116    fn generated_body_masks_high_input_bits_once_before_updates() {
117        let program = multi_hash_program("input", "out", 4);
118        let rendered = format!("{:?}", program.entry());
119        assert!(
120            rendered.contains("255"),
121            "Fix: fused multi_hash must mask u32 byte slots before every checksum update."
122        );
123    }
124}