1use 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
19pub const MULTI_HASH_OP_ID: &str = "vyre-primitives::hash::multi_hash";
21
22#[must_use]
24pub fn multi_hash_reference(bytes: &[u8]) -> (u32, u32, u32) {
25 (crc32(bytes), fnv1a32(bytes), adler32(bytes))
26}
27
28#[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}