pub const FNV1A32_OFFSET: u32 = 0x811c_9dc5;
pub const FNV1A32_PRIME: u32 = 0x0100_0193;
pub const FNV1A64_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
pub const FNV1A64_PRIME: u64 = 0x0000_0100_0000_01b3;
#[must_use]
pub fn fnv1a32(bytes: &[u8]) -> u32 {
let mut h = FNV1A32_OFFSET;
for &byte in bytes {
h ^= u32::from(byte);
h = h.wrapping_mul(FNV1A32_PRIME);
}
h
}
use std::sync::Arc;
use vyre_foundation::ir::model::expr::Ident;
use vyre_foundation::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
pub const FNV1A32_OP_ID: &str = "vyre-primitives::hash::fnv1a32";
pub const FNV1A64_OP_ID: &str = "vyre-primitives::hash::fnv1a64";
const FNV1A64_PRIME_LO: u32 = 0x0000_01B3;
const FNV1A64_PRIME_HI: u32 = 0x0000_0100;
const FNV1A64_OFFSET_LO: u32 = 0x8422_2325;
const FNV1A64_OFFSET_HI: u32 = 0xCBF2_9CE4;
#[must_use]
pub fn fnv1a32_program(input: &str, out: &str, n: u32) -> Program {
fnv1a32_program_bounded(input, out, Expr::u32(n), Some(n))
}
#[must_use]
pub fn fnv1a32_program_dyn(input: &str, out: &str) -> Program {
fnv1a32_program_bounded(input, out, Expr::buf_len(input), None)
}
fn fnv1a32_program_bounded(
input: &str,
out: &str,
loop_bound: Expr,
static_count: Option<u32>,
) -> Program {
let body = vec![Node::Region {
generator: Ident::from(FNV1A32_OP_ID),
source_region: None,
body: Arc::new(vec![Node::if_then(
Expr::eq(Expr::InvocationId { axis: 0 }, Expr::u32(0)),
vec![
Node::let_bind("h", Expr::u32(FNV1A32_OFFSET)),
Node::loop_for(
"i",
Expr::u32(0),
loop_bound,
vec![
Node::let_bind(
"byte",
Expr::bitand(Expr::load(input, Expr::var("i")), Expr::u32(0xFF)),
),
Node::assign("h", Expr::bitxor(Expr::var("h"), Expr::var("byte"))),
Node::assign("h", Expr::mul(Expr::var("h"), Expr::u32(FNV1A32_PRIME))),
],
),
Node::store(out, Expr::u32(0), Expr::var("h")),
],
)]),
}];
let input_buf = match static_count {
Some(n) => {
BufferDecl::storage(input, 0, BufferAccess::ReadOnly, DataType::U32).with_count(n)
}
None => BufferDecl::storage(input, 0, BufferAccess::ReadOnly, DataType::U32),
};
Program::wrapped(
vec![
input_buf,
BufferDecl::output(out, 1, DataType::U32).with_count(1),
],
[1, 1, 1],
body,
)
}
#[must_use]
pub fn fnv1a64_program(input: &str, out: &str) -> Program {
fnv1a64_program_bounded(input, out, Expr::buf_len(input), None)
}
#[must_use]
pub fn fnv1a64_program_n(input: &str, out: &str, n: u32) -> Program {
fnv1a64_program_bounded(input, out, Expr::u32(n), Some(n))
}
fn fnv1a64_program_bounded(
input: &str,
out: &str,
loop_bound: Expr,
static_count: Option<u32>,
) -> Program {
let body = vec![Node::Region {
generator: Ident::from(FNV1A64_OP_ID),
source_region: None,
body: Arc::new(vec![Node::if_then(
Expr::eq(Expr::InvocationId { axis: 0 }, Expr::u32(0)),
vec![
Node::let_bind("h_lo", Expr::u32(FNV1A64_OFFSET_LO)),
Node::let_bind("h_hi", Expr::u32(FNV1A64_OFFSET_HI)),
Node::loop_for(
"i",
Expr::u32(0),
loop_bound,
vec![
Node::assign(
"h_lo",
Expr::bitxor(
Expr::var("h_lo"),
Expr::bitand(Expr::load(input, Expr::var("i")), Expr::u32(0xFF)),
),
),
Node::let_bind(
"lo_lo16",
Expr::bitand(Expr::var("h_lo"), Expr::u32(0xFFFF)),
),
Node::let_bind("lo_hi16", Expr::shr(Expr::var("h_lo"), Expr::u32(16))),
Node::let_bind(
"part_a",
Expr::mul(Expr::var("lo_lo16"), Expr::u32(FNV1A64_PRIME_LO)),
),
Node::let_bind(
"part_b",
Expr::mul(Expr::var("lo_hi16"), Expr::u32(FNV1A64_PRIME_LO)),
),
Node::let_bind("shifted_b", Expr::shl(Expr::var("part_b"), Expr::u32(16))),
Node::let_bind(
"new_lo",
Expr::add(Expr::var("part_a"), Expr::var("shifted_b")),
),
Node::let_bind(
"overflow_bit",
Expr::Select {
cond: Box::new(Expr::gt(
Expr::var("part_a"),
Expr::sub(Expr::u32(u32::MAX), Expr::var("shifted_b")),
)),
true_val: Box::new(Expr::u32(1)),
false_val: Box::new(Expr::u32(0)),
},
),
Node::let_bind(
"carry",
Expr::add(
Expr::shr(Expr::var("part_b"), Expr::u32(16)),
Expr::var("overflow_bit"),
),
),
Node::let_bind(
"hi_times_p_lo",
Expr::mul(Expr::var("h_hi"), Expr::u32(FNV1A64_PRIME_LO)),
),
Node::let_bind(
"lo_times_p_hi",
Expr::mul(Expr::var("h_lo"), Expr::u32(FNV1A64_PRIME_HI)),
),
Node::let_bind(
"new_hi",
Expr::add(
Expr::add(Expr::var("hi_times_p_lo"), Expr::var("lo_times_p_hi")),
Expr::var("carry"),
),
),
Node::assign("h_lo", Expr::var("new_lo")),
Node::assign("h_hi", Expr::var("new_hi")),
],
),
Node::store(out, Expr::u32(0), Expr::var("h_lo")),
Node::store(out, Expr::u32(1), Expr::var("h_hi")),
],
)]),
}];
let input_buf = match static_count {
Some(n) => {
BufferDecl::storage(input, 0, BufferAccess::ReadOnly, DataType::U32).with_count(n)
}
None => BufferDecl::storage(input, 0, BufferAccess::ReadOnly, DataType::U32),
};
Program::wrapped(
vec![
input_buf,
BufferDecl::output(out, 1, DataType::U32).with_count(2),
],
[1, 1, 1],
body,
)
}
#[must_use]
pub fn fnv1a64(bytes: &[u8]) -> u64 {
let mut h = FNV1A64_OFFSET;
for &byte in bytes {
h ^= u64::from(byte);
h = h.wrapping_mul(FNV1A64_PRIME);
}
h
}
#[cfg(feature = "inventory-registry")]
inventory::submit! {
crate::harness::OpEntry::new(
FNV1A32_OP_ID,
|| fnv1a32_program("input", "out", 1),
Some(|| {
let to_bytes = |w: &[u32]| w.iter().flat_map(|v| v.to_le_bytes()).collect::<Vec<u8>>();
vec![vec![
to_bytes(&[0x61]), to_bytes(&[0]), ]]
}),
Some(|| {
let to_bytes = |w: &[u32]| w.iter().flat_map(|v| v.to_le_bytes()).collect::<Vec<u8>>();
vec![vec![to_bytes(&[0xe40c_292c])]] }),
)
}
#[cfg(feature = "inventory-registry")]
inventory::submit! {
crate::harness::OpEntry::new(
FNV1A64_OP_ID,
|| fnv1a64_program("input", "out"),
Some(|| {
let to_bytes = |w: &[u32]| w.iter().flat_map(|v| v.to_le_bytes()).collect::<Vec<u8>>();
vec![vec![
to_bytes(&[0x61]), to_bytes(&[0, 0]), ]]
}),
Some(|| {
let to_bytes = |w: &[u32]| w.iter().flat_map(|v| v.to_le_bytes()).collect::<Vec<u8>>();
vec![vec![to_bytes(&[0x8601_ec8c, 0xaf63_dc4c])]] }),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fnv1a32_empty_is_offset() {
assert_eq!(fnv1a32(b""), FNV1A32_OFFSET);
}
#[test]
fn fnv1a32_single_ascii_a() {
assert_eq!(fnv1a32(b"a"), 0xe40c_292c);
}
#[test]
fn gpu_builder_matches_cpu_ref() {
use vyre_foundation::ir::model::expr::Ident;
let program = fnv1a32_program("src", "out", 5);
match &program.entry()[0] {
Node::Region { generator, .. } => {
assert_eq!(generator, &Ident::from(FNV1A32_OP_ID));
}
other => panic!("expected top-level Region, got {other:?}"),
}
assert_eq!(program.buffers().len(), 2);
}
#[test]
fn fnv1a32_is_deterministic_and_not_identity() {
let a = fnv1a32(b"The quick brown fox");
let b = fnv1a32(b"The quick brown fox");
assert_eq!(a, b);
let c = fnv1a32(b"The quick brown cow");
assert_ne!(a, c);
}
#[test]
fn fnv1a64_empty_is_offset() {
assert_eq!(fnv1a64(b""), FNV1A64_OFFSET);
}
#[test]
fn fnv1a64_single_ascii_a() {
assert_eq!(fnv1a64(b"a"), 0xaf63_dc4c_8601_ec8c);
}
#[test]
fn fnv1a64_matches_fnv1a32_structure() {
let bytes = b"vyre fingerprint";
let h32 = fnv1a32(bytes);
let h64 = fnv1a64(bytes);
assert_ne!(h32 as u64, h64 & 0xffff_ffff);
}
}