use std::sync::Arc;
use vyre_foundation::ir::model::expr::Ident;
use vyre_foundation::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
use super::adler32::{
adler32, adler32_finalize_expr, adler32_initial_a_expr, adler32_initial_b_expr,
adler32_update_byte_nodes,
};
use super::crc32::{crc32, crc32_finalize_expr, crc32_initial_expr, crc32_update_byte_nodes};
use super::fnv1a::{fnv1a32, fnv1a32_initial_expr, fnv1a32_update_byte_node};
pub const MULTI_HASH_OP_ID: &str = "vyre-primitives::hash::multi_hash";
#[must_use]
pub fn multi_hash_reference(bytes: &[u8]) -> (u32, u32, u32) {
(crc32(bytes), fnv1a32(bytes), adler32(bytes))
}
#[must_use]
pub fn multi_hash_program(input: &str, out: &str, n: u32) -> Program {
Program::wrapped(
vec![
BufferDecl::storage(input, 0, BufferAccess::ReadOnly, DataType::U32).with_count(n),
BufferDecl::output(out, 1, DataType::U32).with_count(3),
],
[1, 1, 1],
vec![Node::Region {
generator: Ident::from(MULTI_HASH_OP_ID),
source_region: None,
body: Arc::new(multi_hash_body(input, out, n)),
}],
)
}
fn multi_hash_body(input: &str, out: &str, n: u32) -> Vec<Node> {
vec![Node::if_then(
Expr::eq(Expr::InvocationId { axis: 0 }, Expr::u32(0)),
vec![
Node::let_bind("crc", crc32_initial_expr()),
Node::let_bind("fnv", fnv1a32_initial_expr()),
Node::let_bind("a", adler32_initial_a_expr()),
Node::let_bind("b", adler32_initial_b_expr()),
Node::loop_for("i", Expr::u32(0), Expr::u32(n), {
let mut nodes = vec![Node::let_bind(
"byte",
Expr::bitand(Expr::load(input, Expr::var("i")), Expr::u32(0xFF)),
)];
nodes.extend(crc32_update_byte_nodes("crc", "crc_bit", Expr::var("byte")));
nodes.push(fnv1a32_update_byte_node("fnv", Expr::var("byte")));
nodes.extend(adler32_update_byte_nodes("a", "b", Expr::var("byte")));
nodes
}),
Node::store(out, Expr::u32(0), crc32_finalize_expr(Expr::var("crc"))),
Node::store(out, Expr::u32(1), Expr::var("fnv")),
Node::store(
out,
Expr::u32(2),
adler32_finalize_expr(Expr::var("a"), Expr::var("b")),
),
],
)]
}
#[cfg(feature = "inventory-registry")]
inventory::submit! {
crate::harness::OpEntry::new(
MULTI_HASH_OP_ID,
|| multi_hash_program("input", "out", 3),
Some(|| vec![vec![crate::wire::pack_bytes_as_u32_slice(b"abc")]]),
Some(|| vec![vec![crate::wire::pack_u32_slice(&[
0x3524_41c2,
0x1a47_e90b,
0x024D_0127,
])]]),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reference_matches_constituent_hashes() {
assert_eq!(
multi_hash_reference(b"abc"),
(0x3524_41c2, 0x1a47_e90b, 0x024D_0127)
);
}
#[test]
fn standalone_program_is_single_multi_hash_region() {
let program = multi_hash_program("input", "out", 3);
let [Node::Region { generator, .. }] = program.entry() else {
panic!("expected one primitive multi_hash region");
};
assert_eq!(generator.as_str(), MULTI_HASH_OP_ID);
assert_eq!(program.buffers()[1].count(), 3);
}
#[test]
fn generated_body_masks_high_input_bits_once_before_updates() {
let program = multi_hash_program("input", "out", 4);
let rendered = format!("{:?}", program.entry());
assert!(
rendered.contains("255"),
"Fix: fused multi_hash must mask u32 byte slots before every checksum update."
);
}
}