use vyre_foundation::ir::Program;
const PIPELINE_FINGERPRINT_ALLOWED_FIELDS: &[&str] = &[
"canonical_ir_graph",
"buffer_layout",
"declared_workgroup_size",
"canonical_wire_framing",
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PipelineFingerprint(pub [u8; 32]);
const _: fn(&Program) -> PipelineFingerprint = PipelineFingerprint::of;
impl PipelineFingerprint {
#[must_use]
pub fn of(program: &Program) -> Self {
Self(hash_pipeline_fingerprint(program))
}
#[must_use]
pub fn hex(&self) -> String {
let mut out = String::with_capacity(64);
self.push_hex(&mut out);
out
}
pub(super) fn push_hex(&self, out: &mut String) {
const HEX: &[u8; 16] = b"0123456789abcdef";
for &byte in &self.0 {
out.push(HEX[(byte >> 4) as usize] as char);
out.push(HEX[(byte & 0x0f) as usize] as char);
}
}
}
fn hash_pipeline_fingerprint(program: &Program) -> [u8; 32] {
debug_assert_eq!(
PIPELINE_FINGERPRINT_ALLOWED_FIELDS.len(),
4,
"Fix: update PIPELINE_FINGERPRINT_ALLOWED_FIELDS whenever the fingerprint contract changes."
);
vyre_foundation::optimizer::pipeline_fingerprint_bytes(program)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pipeline_cache::test_helpers::tiny_program;
use vyre_foundation::ir::{BufferDecl, DataType, Expr, Node};
#[test]
fn fingerprint_is_deterministic() {
let a = PipelineFingerprint::of(&tiny_program());
let b = PipelineFingerprint::of(&tiny_program());
assert_eq!(a, b);
}
#[test]
fn fingerprint_hex_is_64_chars() {
let fp = PipelineFingerprint::of(&tiny_program());
assert_eq!(fp.hex().len(), 64);
}
#[test]
fn canonically_equal_programs_share_fingerprint() {
let p1 = Program::wrapped(
vec![BufferDecl::read_write("out", 0, DataType::U32).with_count(1)],
[1, 1, 1],
vec![Node::store(
"out",
Expr::u32(0),
Expr::add(Expr::var("a"), Expr::u32(1)),
)],
);
let p2 = Program::wrapped(
vec![BufferDecl::read_write("out", 0, DataType::U32).with_count(1)],
[1, 1, 1],
vec![Node::store(
"out",
Expr::u32(0),
Expr::add(Expr::u32(1), Expr::var("a")),
)],
);
let fp1 = PipelineFingerprint::of(&p1);
let fp2 = PipelineFingerprint::of(&p2);
assert_eq!(
fp1, fp2,
"canonicalize makes `a+1` and `1+a` share a fingerprint"
);
}
#[test]
fn fingerprint_changes_when_declared_program_shape_changes() {
let base = tiny_program();
let widened = Program::wrapped(
vec![BufferDecl::read_write("out", 0, DataType::U32).with_count(1)],
[64, 1, 1],
vec![Node::store("out", Expr::u32(0), Expr::u32(42))],
);
assert_ne!(
PipelineFingerprint::of(&base),
PipelineFingerprint::of(&widened),
"declared workgroup size is program-intrinsic and must change the fingerprint"
);
}
}